From 8ebf55983eefa5ba390348db45d39bc5df8fe90b Mon Sep 17 00:00:00 2001 From: Dragana <dragana.milovancevic@epfl.ch> Date: Thu, 9 Jun 2022 17:08:31 +0200 Subject: [PATCH] Add 2022-final --- .../concpar22final01/.gitignore | 17 + .../concpar22final01/assignment.sbt | 5 + .../concpar22final01/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final01/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final01/project/plugins.sbt | 2 + .../scala/concpar22final01/Problem1.scala | 65 +++ .../src/main/scala/concpar22final01/lib.scala | 81 +++ .../concpar22final01/Problem1Suite.scala | 491 ++++++++++++++++++ .../concpar22final02/.gitignore | 17 + .../concpar22final02/assignment.sbt | 5 + .../concpar22final02/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final02/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final02/project/plugins.sbt | 2 + .../concpar22final02/AbstractBarrier.scala | 11 + .../main/scala/concpar22final02/Barrier.scala | 16 + .../scala/concpar22final02/ImageLib.scala | 47 ++ .../scala/concpar22final02/Problem2.scala | 29 ++ .../instrumentation/Monitor.scala | 32 ++ .../instrumentation/Stats.scala | 19 + .../concpar22final02/Problem2Suite.scala | 413 +++++++++++++++ .../instrumentation/MockedMonitor.scala | 57 ++ .../instrumentation/SchedulableBarrier.scala | 20 + .../instrumentation/Scheduler.scala | 294 +++++++++++ .../instrumentation/TestHelper.scala | 127 +++++ .../instrumentation/TestUtils.scala | 14 + .../concpar22final03/.gitignore | 17 + .../concpar22final03/assignment.sbt | 5 + .../concpar22final03/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final03/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final03/project/plugins.sbt | 2 + .../scala/concpar22final03/Economics.scala | 44 ++ .../scala/concpar22final03/Problem3.scala | 52 ++ .../concpar22final03/EconomicsTest.scala | 98 ++++ .../concpar22final03/Problem3Suite.scala | 201 +++++++ .../concpar22final04/.gitignore | 17 + .../concpar22final04/assignment.sbt | 5 + .../concpar22final04/build.sbt | 23 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final04/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final04/project/plugins.sbt | 2 + .../scala/concpar22final04/Problem4.scala | 220 ++++++++ .../concpar22final04/Problem4Suite.scala | 361 +++++++++++++ previous-exams/2022-final/concpar22final01.md | 102 ++++ .../2022-final/concpar22final01/.gitignore | 17 + .../concpar22final01/assignment.sbt | 5 + .../2022-final/concpar22final01/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final01/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final01/project/plugins.sbt | 2 + .../scala/concpar22final01/Problem1.scala | 31 ++ .../src/main/scala/concpar22final01/lib.scala | 81 +++ .../concpar22final01/Problem1Suite.scala | 491 ++++++++++++++++++ previous-exams/2022-final/concpar22final02.md | 38 ++ .../2022-final/concpar22final02/.gitignore | 17 + .../concpar22final02/assignment.sbt | 5 + .../2022-final/concpar22final02/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final02/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final02/project/plugins.sbt | 2 + .../concpar22final02/AbstractBarrier.scala | 11 + .../main/scala/concpar22final02/Barrier.scala | 7 + .../scala/concpar22final02/ImageLib.scala | 47 ++ .../scala/concpar22final02/Problem2.scala | 12 + .../instrumentation/Monitor.scala | 32 ++ .../instrumentation/Stats.scala | 19 + .../concpar22final02/Problem2Suite.scala | 413 +++++++++++++++ .../instrumentation/MockedMonitor.scala | 57 ++ .../instrumentation/SchedulableBarrier.scala | 20 + .../instrumentation/Scheduler.scala | 294 +++++++++++ .../instrumentation/TestHelper.scala | 127 +++++ .../instrumentation/TestUtils.scala | 14 + previous-exams/2022-final/concpar22final03.md | 37 ++ .../2022-final/concpar22final03/.gitignore | 17 + .../concpar22final03/assignment.sbt | 5 + .../2022-final/concpar22final03/build.sbt | 11 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final03/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final03/project/plugins.sbt | 2 + .../scala/concpar22final03/Economics.scala | 44 ++ .../scala/concpar22final03/Problem3.scala | 38 ++ .../concpar22final03/EconomicsTest.scala | 98 ++++ .../concpar22final03/Problem3Suite.scala | 201 +++++++ previous-exams/2022-final/concpar22final04.md | 98 ++++ .../2022-final/concpar22final04/.gitignore | 17 + .../concpar22final04/assignment.sbt | 5 + .../2022-final/concpar22final04/build.sbt | 23 + .../project/CourseraStudent.scala | 212 ++++++++ .../project/MOOCSettings.scala | 51 ++ .../project/StudentTasks.scala | 150 ++++++ .../concpar22final04/project/build.properties | 1 + .../project/buildSettings.sbt | 5 + .../concpar22final04/project/plugins.sbt | 2 + .../scala/concpar22final04/Problem4.scala | 179 +++++++ .../concpar22final04/Problem4Suite.scala | 361 +++++++++++++ previous-exams/2022-final/spotify.jpg | Bin 0 -> 343099 bytes 119 files changed, 9200 insertions(+) create mode 100644 previous-exams/2022-final-solutions/concpar22final01/.gitignore create mode 100644 previous-exams/2022-final-solutions/concpar22final01/assignment.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final01/build.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/StudentTasks.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/build.properties create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/buildSettings.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final01/project/plugins.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/Problem1.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/lib.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/.gitignore create mode 100644 previous-exams/2022-final-solutions/concpar22final02/assignment.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final02/build.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/StudentTasks.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/build.properties create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/buildSettings.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final02/project/plugins.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Barrier.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Problem2.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/.gitignore create mode 100644 previous-exams/2022-final-solutions/concpar22final03/assignment.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final03/build.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/StudentTasks.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/build.properties create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/buildSettings.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final03/project/plugins.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Economics.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Problem3.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final04/.gitignore create mode 100644 previous-exams/2022-final-solutions/concpar22final04/assignment.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final04/build.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/StudentTasks.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/build.properties create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/buildSettings.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final04/project/plugins.sbt create mode 100644 previous-exams/2022-final-solutions/concpar22final04/src/main/scala/concpar22final04/Problem4.scala create mode 100644 previous-exams/2022-final-solutions/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala create mode 100644 previous-exams/2022-final/concpar22final01.md create mode 100644 previous-exams/2022-final/concpar22final01/.gitignore create mode 100644 previous-exams/2022-final/concpar22final01/assignment.sbt create mode 100644 previous-exams/2022-final/concpar22final01/build.sbt create mode 100644 previous-exams/2022-final/concpar22final01/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final/concpar22final01/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final/concpar22final01/project/StudentTasks.scala create mode 100644 previous-exams/2022-final/concpar22final01/project/build.properties create mode 100644 previous-exams/2022-final/concpar22final01/project/buildSettings.sbt create mode 100644 previous-exams/2022-final/concpar22final01/project/plugins.sbt create mode 100644 previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/Problem1.scala create mode 100644 previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/lib.scala create mode 100644 previous-exams/2022-final/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala create mode 100644 previous-exams/2022-final/concpar22final02.md create mode 100644 previous-exams/2022-final/concpar22final02/.gitignore create mode 100644 previous-exams/2022-final/concpar22final02/assignment.sbt create mode 100644 previous-exams/2022-final/concpar22final02/build.sbt create mode 100644 previous-exams/2022-final/concpar22final02/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final/concpar22final02/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final/concpar22final02/project/StudentTasks.scala create mode 100644 previous-exams/2022-final/concpar22final02/project/build.properties create mode 100644 previous-exams/2022-final/concpar22final02/project/buildSettings.sbt create mode 100644 previous-exams/2022-final/concpar22final02/project/plugins.sbt create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Barrier.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Problem2.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala create mode 100644 previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala create mode 100644 previous-exams/2022-final/concpar22final03.md create mode 100644 previous-exams/2022-final/concpar22final03/.gitignore create mode 100644 previous-exams/2022-final/concpar22final03/assignment.sbt create mode 100644 previous-exams/2022-final/concpar22final03/build.sbt create mode 100644 previous-exams/2022-final/concpar22final03/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final/concpar22final03/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final/concpar22final03/project/StudentTasks.scala create mode 100644 previous-exams/2022-final/concpar22final03/project/build.properties create mode 100644 previous-exams/2022-final/concpar22final03/project/buildSettings.sbt create mode 100644 previous-exams/2022-final/concpar22final03/project/plugins.sbt create mode 100644 previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Economics.scala create mode 100644 previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Problem3.scala create mode 100644 previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala create mode 100644 previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala create mode 100644 previous-exams/2022-final/concpar22final04.md create mode 100644 previous-exams/2022-final/concpar22final04/.gitignore create mode 100644 previous-exams/2022-final/concpar22final04/assignment.sbt create mode 100644 previous-exams/2022-final/concpar22final04/build.sbt create mode 100644 previous-exams/2022-final/concpar22final04/project/CourseraStudent.scala create mode 100644 previous-exams/2022-final/concpar22final04/project/MOOCSettings.scala create mode 100644 previous-exams/2022-final/concpar22final04/project/StudentTasks.scala create mode 100644 previous-exams/2022-final/concpar22final04/project/build.properties create mode 100644 previous-exams/2022-final/concpar22final04/project/buildSettings.sbt create mode 100644 previous-exams/2022-final/concpar22final04/project/plugins.sbt create mode 100644 previous-exams/2022-final/concpar22final04/src/main/scala/concpar22final04/Problem4.scala create mode 100644 previous-exams/2022-final/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala create mode 100644 previous-exams/2022-final/spotify.jpg diff --git a/previous-exams/2022-final-solutions/concpar22final01/.gitignore b/previous-exams/2022-final-solutions/concpar22final01/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final-solutions/concpar22final01/assignment.sbt b/previous-exams/2022-final-solutions/concpar22final01/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final-solutions/concpar22final01/build.sbt b/previous-exams/2022-final-solutions/concpar22final01/build.sbt new file mode 100644 index 0000000..beb0e5c --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final01" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/CourseraStudent.scala b/previous-exams/2022-final-solutions/concpar22final01/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/MOOCSettings.scala b/previous-exams/2022-final-solutions/concpar22final01/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/StudentTasks.scala b/previous-exams/2022-final-solutions/concpar22final01/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/build.properties b/previous-exams/2022-final-solutions/concpar22final01/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/buildSettings.sbt b/previous-exams/2022-final-solutions/concpar22final01/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final-solutions/concpar22final01/project/plugins.sbt b/previous-exams/2022-final-solutions/concpar22final01/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/Problem1.scala b/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/Problem1.scala new file mode 100644 index 0000000..d555c0d --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/Problem1.scala @@ -0,0 +1,65 @@ +package concpar22final01 + +trait Problem1 extends Lib: + + class DLLCombinerImplementation extends DLLCombiner: + + // Copies every other Integer element of data array, starting from the first (index 0), up to the middle + def task1(data: Array[Int]) = task { + + var current = first + var i = 0 + while current != null && i < size / 2 do + data(i) = current.value + i += 2 + current = current.getNext2 + } + + // Copies every other Integer element of data array, starting from the second, up to the middle + def task2(data: Array[Int]) = task { + + var current = second + var i = 1 + while current != null && i < size / 2 do + data(i) = current.value + i += 2 + current = current.getNext2 + } + + // Copies every other Integer element of data array, starting from the second to last, up to the middle + def task3(data: Array[Int]) = task { + + var current = secondToLast + var i = size - 2 + while current != null && i >= size / 2 do + data(i) = current.value + i -= 2 + current = current.getPrevious2 + } + + // Copies every other Integer element of data array, starting from the last, up to the middle + // This is executed on the current thread. + def task4(data: Array[Int]) = + + var current = last + var i = size - 1 + while current != null && i >= size / 2 do + data(i) = current.value + i -= 2 + current = current.getPrevious2 + + def result(): Array[Int] = + val data = new Array[Int](size) + + val t1 = task1(data) + val t2 = task2(data) + val t3 = task3(data) + + task4(data) + + t1.join() + t2.join() + t3.join() + + + data diff --git a/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/lib.scala b/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/lib.scala new file mode 100644 index 0000000..6d9d6ee --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/src/main/scala/concpar22final01/lib.scala @@ -0,0 +1,81 @@ +package concpar22final01 + +import java.util.concurrent.* +import scala.util.DynamicVariable + +trait Lib: + class Node(val value: Int): + protected var next: Node = null // null for last node. + protected var next2: Node = null // null for last node. + protected var previous: Node = null // null for first node. + protected var previous2: Node = null // null for first node. + + def getNext: Node = next // do NOT use in the result method + def getNext2: Node = next2 + def getPrevious: Node = previous // do NOT use in the result method + def getPrevious2: Node = previous2 + + def setNext(n: Node): Unit = next = n + def setNext2(n: Node): Unit = next2 = n + def setPrevious(n: Node): Unit = previous = n + def setPrevious2(n: Node): Unit = previous2 = n + + // Simplified Combiner interface + // Implements methods += and combine + // Abstract methods should be implemented in subclasses + abstract class DLLCombiner: + var first: Node = null // null for empty lists. + var last: Node = null // null for empty lists. + + var second: Node = null // null for empty lists. + var secondToLast: Node = null // null for empty lists. + + var size: Int = 0 + + // Adds an Integer to this array combiner. + def +=(elem: Int): Unit = + val node = new Node(elem) + if size == 0 then + first = node + last = node + size = 1 + else + last.setNext(node) + node.setPrevious(last) + node.setPrevious2(last.getPrevious) + if size > 1 then last.getPrevious.setNext2(node) + else second = node + secondToLast = last + last = node + size += 1 + + // Combines this array combiner and another given combiner in constant O(1) complexity. + def combine(that: DLLCombiner): DLLCombiner = + if this.size == 0 then that + else if that.size == 0 then this + else + this.last.setNext(that.first) + this.last.setNext2(that.first.getNext) + if this.last.getPrevious != null then + this.last.getPrevious.setNext2(that.first) // important + + that.first.setPrevious(this.last) + that.first.setPrevious2(this.last.getPrevious) + if that.first.getNext != null then that.first.getNext.setPrevious2(this.last) // important + + if this.size == 1 then second = that.first + + this.size = this.size + that.size + this.last = that.last + this.secondToLast = that.secondToLast + + this + + def task1(data: Array[Int]): ForkJoinTask[Unit] + def task2(data: Array[Int]): ForkJoinTask[Unit] + def task3(data: Array[Int]): ForkJoinTask[Unit] + def task4(data: Array[Int]): Unit + + def result(): Array[Int] + + def task[T](body: => T): ForkJoinTask[T] diff --git a/previous-exams/2022-final-solutions/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala b/previous-exams/2022-final-solutions/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala new file mode 100644 index 0000000..a650072 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala @@ -0,0 +1,491 @@ +package concpar22final01 + +import java.util.concurrent.* +import scala.util.DynamicVariable + +class Problem1Suite extends AbstractProblem1Suite: + + test("[Public] fetch simple result without combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + combiner1 += 1 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + + val result = combiner1.result() + val array = Array(7, 2, 3, 8, 1, 2, 3, 8) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result without combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + combiner1 += 1 + + val result = combiner1.result() + val array = Array(7, 2, 3, 8, 1) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result after simple combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + + val combiner2 = new DLLCombinerTest + combiner2 += 3 + combiner2 += 8 + + val combiner3 = new DLLCombinerTest + combiner3 += 1 + combiner3 += 9 + + val combiner4 = new DLLCombinerTest + combiner4 += 3 + combiner4 += 2 + + val result = combiner1.combine(combiner2).combine(combiner3).combine(combiner4).result() + val array = Array(7, 2, 3, 8, 1, 9, 3, 2) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result - small combiner (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 4 + combiner1 += 2 + combiner1 += 6 + + val result = combiner1.result() + val array = Array(4, 2, 6) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + + // (25+) 15 / 250 points for correct implementation, don't check parallelism + test("[Correctness] fetch result - simple combiners (2pts)") { + assertCorrectnessSimple() + } + + test("[Correctness] fetch result - small combiners (3pts)") { + assertCorrectnessBasic() + } + + test("[Correctness] fetch result - small combiners after combining (5pts)") { + assertCorrectnessCombined() + } + + test("[Correctness] fetch result - large combiners (5pts)") { + assertCorrectnessLarge() + } + + def assertCorrectnessSimple() = simpleCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessBasic() = basicCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessCombined() = + combinedCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessLarge() = largeCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + // (25+15+) 25 / 250 points for correct parallel implementation, don't check if it's exactly 1/4 of the array per task + private var count = 0 + private val expected = 3 + + override def task[T](body: => T): ForkJoinTask[T] = + count += 1 + scheduler.value.schedule(body) + + test("[TaskCount] number of newly created tasks should be 3 (5pts)") { + assertTaskCountSimple() + } + + test("[TaskCount] fetch result and check parallel - simple combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessSimple() + } + + test("[TaskCount] fetch result and check parallel - small combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessBasic() + } + + test("[TaskCount] fetch result and check parallel - small combiners after combining (5pts)") { + assertTaskCountSimple() + assertCorrectnessCombined() + } + + test("[TaskCount] fetch result and check parallel - large combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessLarge() + } + + def assertTaskCountSimple(): Unit = + simpleCombiners.foreach(elem => assertTaskCount(elem._1, elem._2)) + + def assertTaskCount(combiner: DLLCombinerTest, array: Array[Int]): Unit = + try + count = 0 + build(combiner, array) + combiner.result() + assertEquals( + count, + expected, { + s"ERROR: Expected $expected instead of $count calls to `task(...)`" + } + ) + finally count = 0 + + // (25+15+25+) 50 / 250 points for correct implementation that uses only next2 and previous2, and not next and previous + test("[Skip2] fetch parallel result and check skip2 - simple combiners (10pts)") { + assertTaskCountSimple() + assertSkipSimple() + assertCorrectnessSimple() + } + + test("[Skip2] fetch result and check skip2 - simple combiners (10pts)") { + assertSkipSimple() + assertCorrectnessSimple() + } + + test("[Skip2] fetch result and check skip2 - small combiners (10pts)") { + assertSkipSimple() + assertCorrectnessBasic() + } + + test("[Skip2] fetch result and check skip2 - small combiners after combining (10pts)") { + assertSkipSimple() + assertCorrectnessCombined() + } + + test("[Skip2] fetch result and check skip2 - large combiners (10pts)") { + assertSkipSimple() + assertCorrectnessLarge() + } + + def assertSkipSimple(): Unit = simpleCombiners.foreach(elem => assertSkip(elem._1, elem._2)) + + def assertSkip(combiner: DLLCombinerTest, array: Array[Int]): Unit = + build(combiner, array) + combiner.result() + assertEquals( + combiner.nonSkipped, + false, { + s"ERROR: Calls to 'next' and 'previous' are not allowed! You should only use 'next2` and 'previous2' in your solution." + } + ) + + // (25+15+25+50+) 75 / 250 points for correct parallel implementation, exactly 1/4 of the array per task + test("[TaskFairness] each task should compute 1/4 of the result (15pts)") { + assertTaskFairness(simpleCombiners.unzip._1) + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - simple combiners (15pts)" + ) { + assertTaskFairness(simpleCombiners.unzip._1) + assertCorrectnessSimple() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - small combiners (15pts)" + ) { + assertTaskFairness(basicCombiners.unzip._1) + assertCorrectnessBasic() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - small combiners after combining (15pts)" + ) { + assertTaskFairness(combinedCombiners.unzip._1) + assertCorrectnessCombined() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - large combiners (15pts)" + ) { + assertTaskFairness(largeCombiners.unzip._1) + assertCorrectnessLarge() + } + + def assertTaskFairness(combiners: List[DLLCombiner]): Unit = + def assertNewTaskFairness(combiner: DLLCombiner, task: ForkJoinTask[Unit], data: Array[Int]) = + var count = 0 + var expected = combiner.size / 4 + task.join + count = data.count(elem => elem != 0) + assert((count - expected).abs <= 1) + + def assertMainTaskFairness(combiner: DLLCombiner, task: Unit, data: Array[Int]) = + var count = 0 + var expected = combiner.size / 4 + count = data.count(elem => elem != 0) + assert((count - expected).abs <= 1) + + combiners.foreach { elem => + var data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task1(data), data) + + data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task2(data), data) + + data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task3(data), data) + + data = Array.fill(elem.size)(0) + assertMainTaskFairness(elem, elem.task4(data), data) + } + + // (25+15+25+50+75+) 60 / 250 points for correct parallel implementation, exactly 1/4 of the array per task, exactly the specified quarter + + test( + "[TaskPrecision] each task should compute specified 1/4 of the result - simple combiners (10pts)" + ) { + assertTaskPrecision(simpleCombiners) + } + + test( + "[TaskPrecision] task1 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision1(simpleCombiners) + } + + test( + "[TaskPrecision] task2 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision2(simpleCombiners) + } + + test( + "[TaskPrecision] task3 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision3(simpleCombiners) + } + + test( + "[TaskPrecision] task4 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision4(simpleCombiners) + } + + test( + "[TaskPrecision] each task should compute specified 1/4 of the result - other combiners (30pts)" + ) { + assertTaskPrecision(basicCombiners) + assertTaskPrecision(combinedCombiners) + assertTaskPrecision(largeCombiners) + } + + def assertTaskPrecision(combiners: List[(DLLCombiner, Array[Int])]): Unit = + assertTaskPrecision1(combiners) + assertTaskPrecision2(combiners) + assertTaskPrecision3(combiners) + assertTaskPrecision4(combiners) + + def assertTaskPrecision1(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task1 = elem._1.task1(data) + task1.join + Range(0, elem._1.size).foreach(i => + (if i < elem._1.size / 2 - 1 && i % 2 == 0 then ref(i) = elem._2(i)) + ) + assert(Range(0, elem._1.size / 2 - 1).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision2(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task2 = elem._1.task2(data) + task2.join + Range(0, elem._1.size).foreach(i => + (if i < elem._1.size / 2 - 1 && i % 2 == 1 then ref(i) = elem._2(i)) + ) + assert(Range(0, elem._1.size / 2 - 1).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision3(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task3 = elem._1.task3(data) + task3.join + Range(0, elem._1.size).foreach(i => + (if i > elem._1.size / 2 + 1 && i % 2 == elem._1.size % 2 then ref(i) = elem._2(i)) + ) + assert(Range(elem._1.size / 2 + 2, elem._1.size).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision4(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task4 = elem._1.task4(data) + Range(0, elem._1.size).foreach(i => + (if i > elem._1.size / 2 + 1 && i % 2 != elem._1.size % 2 then ref(i) = elem._2(i)) + ) + assert(Range(elem._1.size / 2 + 2, elem._1.size).forall(i => data(i) == ref(i))) + } + +trait AbstractProblem1Suite extends munit.FunSuite with LibImpl: + + def simpleCombiners = buildSimpleCombiners() + def basicCombiners = buildBasicCombiners() + def combinedCombiners = buildCombinedCombiners() + def largeCombiners = buildLargeCombiners() + + def buildSimpleCombiners() = + val simpleCombiners = List( + (new DLLCombinerTest, Array(4, 2, 6, 1, 5, 4, 3, 5, 6, 3, 4, 5, 6, 3, 4, 5)), + (new DLLCombinerTest, Array(7, 2, 2, 9, 3, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2)), + (new DLLCombinerTest, Array.fill(16)(5)) + ) + simpleCombiners.foreach(elem => build(elem._1, elem._2)) + simpleCombiners + + def buildBasicCombiners() = + val basicCombiners = List( + (new DLLCombinerTest, Array(4, 2, 6)), + (new DLLCombinerTest, Array(4, 1, 6)), + (new DLLCombinerTest, Array(7, 2, 2, 9, 3, 2, 11, 12, 5, 14, 15, 1, 17, 23)), + (new DLLCombinerTest, Array(7, 2, 9, 9, 3, 2, 11, 12, 13, 14, 15, 16, 17, 22)), + (new DLLCombinerTest, Array.fill(16)(7)), + (new DLLCombinerTest, Array.fill(16)(4)), + (new DLLCombinerTest, Array.fill(5)(3)), + (new DLLCombinerTest, Array.fill(5)(7)), + (new DLLCombinerTest, Array.fill(5)(4)) + ) + basicCombiners.foreach(elem => build(elem._1, elem._2)) + basicCombiners + + def buildCombinedCombiners() = + var combinedCombiners = List[(DLLCombiner, Array[Int])]() + + Range(1, 10).foreach { n => + val array = basicCombiners.filter(elem => elem._1.size == n).foldLeft(Array[Int]()) { + (acc, i) => acc ++ i._2 + } + val empty: DLLCombiner = new DLLCombinerTest + val combiner = basicCombiners.filter(elem => elem._1.size == n).map(_._1).foldLeft(empty) { + (acc, c) => acc.combine(c) + } + + combinedCombiners = combinedCombiners :+ (combiner, array) + } + combinedCombiners + + def buildLargeCombiners() = + val largeCombiners = List( + (new DLLCombinerTest, Array.fill(1321)(4) ++ Array.fill(1322)(7)), + (new DLLCombinerTest, Array.fill(1341)(2) ++ Array.fill(1122)(5)), + ( + new DLLCombinerTest, + Array.fill(1321)(4) ++ Array.fill(1322)(7) ++ Array.fill(321)(4) ++ Array.fill(322)(7) + ), + (new DLLCombinerTest, Array.fill(992321)(4) ++ Array.fill(99322)(7)), + (new DLLCombinerTest, Array.fill(953211)(4) ++ Array.fill(999322)(1)) + ) + largeCombiners.foreach(elem => build(elem._1, elem._2)) + largeCombiners + + def build(combiner: DLLCombinerTest, array: Array[Int]): DLLCombinerTest = + array.foreach(elem => combiner += elem) + combiner + + def compare(combiner: DLLCombiner, array: Array[Int]): Boolean = + val result = combiner.result() + Range(0, array.size).forall(i => array(i) == result(i)) + + def buildAndCompare(combiner: DLLCombinerTest, array: Array[Int]): Boolean = + array.foreach(elem => combiner += elem) + val result = combiner.result() + Range(0, array.size).forall(i => array(i) == result(i)) + +trait LibImpl extends Problem1: + + val forkJoinPool = new ForkJoinPool + + abstract class TaskScheduler: + def schedule[T](body: => T): ForkJoinTask[T] + + class DefaultTaskScheduler extends TaskScheduler: + def schedule[T](body: => T): ForkJoinTask[T] = + val t = new RecursiveTask[T]: + def compute = body + Thread.currentThread match + case wt: ForkJoinWorkerThread => + t.fork() + case _ => + forkJoinPool.execute(t) + t + + val scheduler = new DynamicVariable[TaskScheduler](new DefaultTaskScheduler) + + def task[T](body: => T): ForkJoinTask[T] = scheduler.value.schedule(body) + + class NodeTest(val v: Int, val myCombiner: DLLCombinerTest) extends Node(v): + override def getNext: Node = + myCombiner.nonSkipped = true + next + override def getNext2: Node = next2 + override def getPrevious: Node = + myCombiner.nonSkipped = true + previous + override def getPrevious2: Node = previous2 + override def setNext(n: Node): Unit = next = n + override def setNext2(n: Node): Unit = next2 = n + override def setPrevious(n: Node): Unit = previous = n + override def setPrevious2(n: Node): Unit = previous2 = n + + class DLLCombinerTest extends DLLCombinerImplementation: + var nonSkipped = false + override def result(): Array[Int] = + nonSkipped = false + super.result() + override def +=(elem: Int): Unit = + val node = new NodeTest(elem, this) + if size == 0 then + first = node + last = node + size = 1 + else + last.setNext(node) + node.setPrevious(last) + node.setPrevious2(last.getPrevious) + if size > 1 then last.getPrevious.setNext2(node) + else second = node + secondToLast = last + last = node + size += 1 + override def combine(that: DLLCombiner): DLLCombiner = + if this.size == 0 then that + else if that.size == 0 then this + else + this.last.setNext(that.first) + this.last.setNext2(that.first.getNext) + if this.last.getPrevious != null then + this.last.getPrevious.setNext2(that.first) // important + + that.first.setPrevious(this.last) + that.first.setPrevious2(this.last.getPrevious) + if that.first.getNext != null then that.first.getNext.setPrevious2(this.last) // important + + if this.size == 1 then second = that.first + + this.size = this.size + that.size + this.last = that.last + this.secondToLast = that.secondToLast + + this diff --git a/previous-exams/2022-final-solutions/concpar22final02/.gitignore b/previous-exams/2022-final-solutions/concpar22final02/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final-solutions/concpar22final02/assignment.sbt b/previous-exams/2022-final-solutions/concpar22final02/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final-solutions/concpar22final02/build.sbt b/previous-exams/2022-final-solutions/concpar22final02/build.sbt new file mode 100644 index 0000000..61e5a6e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final02" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "0.7.26" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/CourseraStudent.scala b/previous-exams/2022-final-solutions/concpar22final02/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/MOOCSettings.scala b/previous-exams/2022-final-solutions/concpar22final02/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/StudentTasks.scala b/previous-exams/2022-final-solutions/concpar22final02/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/build.properties b/previous-exams/2022-final-solutions/concpar22final02/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/buildSettings.sbt b/previous-exams/2022-final-solutions/concpar22final02/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final-solutions/concpar22final02/project/plugins.sbt b/previous-exams/2022-final-solutions/concpar22final02/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala new file mode 100644 index 0000000..decdbc9 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala @@ -0,0 +1,11 @@ +package concpar22final02 + +import instrumentation.Monitor + +abstract class AbstractBarrier(val numThreads: Int) extends Monitor: + + var count = numThreads + + def awaitZero(): Unit + + def countDown(): Unit diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Barrier.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Barrier.scala new file mode 100644 index 0000000..68066d2 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Barrier.scala @@ -0,0 +1,16 @@ +package concpar22final02 + +class Barrier(numThreads: Int) extends AbstractBarrier(numThreads): + + + def awaitZero(): Unit = + synchronized { + while count > 0 do wait() + } + + + def countDown(): Unit = + synchronized { + count -= 1 + if count <= 0 then notifyAll() + } diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala new file mode 100644 index 0000000..0f538ea --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala @@ -0,0 +1,47 @@ +package concpar22final02 + +import scala.collection.mutable.ArrayBuffer + +class ImageLib(size: Int): + + val buffer1: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(1) + val buffer2: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(0) + + enum Filter(val kernel: Array[Array[Int]]): + case Outline extends Filter(Array(Array(-1, -1, -1), Array(-1, 8, -1), Array(-1, -1, -1))) + case Sharpen extends Filter(Array(Array(0, -1, 0), Array(-1, 5, -1), Array(0, -1, 0))) + case Emboss extends Filter(Array(Array(-2, -1, 0), Array(-1, 1, 1), Array(0, 1, 2))) + case Identity extends Filter(Array(Array(0, 0, 0), Array(0, 1, 0), Array(0, 0, 0))) + + def init(input: ArrayBuffer[ArrayBuffer[Int]]) = + for i <- 0 to size - 1 do + for j <- 0 to size - 1 do + buffer1(i)(j) = input(i)(j) + + def computeConvolution( + kernel: Array[Array[Int]], + input: ArrayBuffer[ArrayBuffer[Int]], + row: Int, + column: Int + ): Int = + + val displacement = Array(-1, 0, 1) + var output = 0 + + for i <- 0 to 2 do + for j <- 0 to 2 do + val newI = row + displacement(i) + val newJ = column + displacement(j) + if newI < 0 || newI >= size || newJ < 0 || newJ >= size then output += 0 + else output += (kernel(i)(j) * input(newI)(newJ)) + + output + + def applyFilter( + kernel: Array[Array[Int]], + input: ArrayBuffer[ArrayBuffer[Int]], + output: ArrayBuffer[ArrayBuffer[Int]], + row: Int + ): Unit = + for i <- 0 to input(row).size - 1 do + output(row)(i) = computeConvolution(kernel, input, row, i) diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Problem2.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Problem2.scala new file mode 100644 index 0000000..84e63b5 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/Problem2.scala @@ -0,0 +1,29 @@ +package concpar22final02 + +import java.util.concurrent.atomic.AtomicInteger +import scala.collection.mutable.ArrayBuffer + +class Problem2(imageSize: Int, numThreads: Int, numFilters: Int): + + val barrier: ArrayBuffer[Barrier] = ArrayBuffer.fill(numFilters)(Barrier(numThreads)) + + val imageLib: ImageLib = ImageLib(imageSize) + + + def imagePipeline( + filters: Array[imageLib.Filter], + rows: Array[Int] + ): ArrayBuffer[ArrayBuffer[Int]] = + for i <- 0 to filters.size - 1 do + for j <- 0 to rows.size - 1 do + if i % 2 == 0 then + imageLib.applyFilter(filters(i).kernel, imageLib.buffer1, imageLib.buffer2, rows(j)) + else + imageLib.applyFilter(filters(i).kernel, imageLib.buffer2, imageLib.buffer1, rows(j)) + + barrier(i).countDown() + barrier(i).awaitZero() + + if filters.size % 2 == 0 then imageLib.buffer1 + else imageLib.buffer2 + diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala new file mode 100644 index 0000000..2718337 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala @@ -0,0 +1,32 @@ +package concpar22final02.instrumentation + +class Dummy + +trait Monitor: + implicit val dummy: Dummy = new Dummy + + def wait()(implicit i: Dummy) = waitDefault() + + def synchronized[T](e: => T)(implicit i: Dummy) = synchronizedDefault(e) + + def notify()(implicit i: Dummy) = notifyDefault() + + def notifyAll()(implicit i: Dummy) = notifyAllDefault() + + private val lock = new AnyRef + + // Can be overriden. + def waitDefault(): Unit = lock.wait() + def synchronizedDefault[T](toExecute: => T): T = lock.synchronized(toExecute) + def notifyDefault(): Unit = lock.notify() + def notifyAllDefault(): Unit = lock.notifyAll() + +trait LockFreeMonitor extends Monitor: + override def waitDefault() = + throw new Exception("Please use lock-free structures and do not use wait()") + override def synchronizedDefault[T](toExecute: => T): T = + throw new Exception("Please use lock-free structures and do not use synchronized()") + override def notifyDefault() = + throw new Exception("Please use lock-free structures and do not use notify()") + override def notifyAllDefault() = + throw new Exception("Please use lock-free structures and do not use notifyAll()") diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala new file mode 100644 index 0000000..fb4a31e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala @@ -0,0 +1,19 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ +package concpar22final02.instrumentation + +import java.lang.management.* + +/** A collection of methods that can be used to collect run-time statistics about Leon programs. + * This is mostly used to test the resources properties of Leon programs + */ +object Stats: + def timed[T](code: => T)(cont: Long => Unit): T = + var t1 = System.currentTimeMillis() + val r = code + cont((System.currentTimeMillis() - t1)) + r + + def withTime[T](code: => T): (T, Long) = + var t1 = System.currentTimeMillis() + val r = code + (r, (System.currentTimeMillis() - t1)) diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala new file mode 100644 index 0000000..95da2fc --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala @@ -0,0 +1,413 @@ +package concpar22final02 + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.mutable.HashMap +import scala.util.Random +import instrumentation.SchedulableProblem2 + +import instrumentation.TestHelper.* +import instrumentation.TestUtils.* +import scala.collection.mutable.ArrayBuffer + +class Problem2Suite extends munit.FunSuite: + + val imageSize = 5 + val nThreads = 3 + + def rowsForThread(threadNumber: Int): Array[Int] = + val start: Int = (imageSize * threadNumber) / nThreads + val end: Int = (imageSize * (threadNumber + 1)) / nThreads + (start until end).toArray + + test("Should work when barrier is called by a single thread (10pts)") { + testManySchedules( + 1, + sched => + val temp = new Problem2(imageSize, 1, 1) + ( + List(() => temp.barrier(0).countDown()), + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then + val notifyCount = sched.notifyCount + val notifyAllCount = sched.notifyAllCount + (false, s"No notify call $notifyCount $notifyAllCount") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when a single thread processes a single filter (10pts)") { + val temp = new Problem2(imageSize, 1, 1) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline(Array(temp.imageLib.Filter.Outline), Array(0, 1, 2, 3, 4)) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + } + + test("Should work when a single thread processes a 2 same filters (15pts)") { + val temp = new Problem2(imageSize, 1, 2) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array(temp.imageLib.Filter.Identity, temp.imageLib.Filter.Identity), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + } + + test("Should work when a single thread processes a 2 different filters (15pts)") { + val temp = new Problem2(imageSize, 1, 2) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array(temp.imageLib.Filter.Identity, temp.imageLib.Filter.Outline), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + } + + test("Should work when barrier is called by two threads (25pts)") { + testManySchedules( + 2, + sched => + val temp = new Problem2(imageSize, 2, 1) + ( + List( + () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + , + () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + ), + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then (false, s"No notify call") + else if sched.waitCount == 0 then (false, s"No wait call") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when barrier is called by multiple threads (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new Problem2(imageSize, nThreads, 1) + ( + (for i <- 0 until nThreads yield () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + ).toList, + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then (false, s"No notify call") + else if sched.waitCount == 0 then (false, s"No wait call") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when a single thread processes a multiple same filters (25pts)") { + val temp = new Problem2(imageSize, 1, 3) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array( + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Outline + ), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-128, -173, -107, -173, -128), + ArrayBuffer(205, -2, 172, -2, 205), + ArrayBuffer(322, -128, 208, -128, 322), + ArrayBuffer(55, -854, -428, -854, 55), + ArrayBuffer(1180, 433, 751, 433, 1180) + ) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-16, -22, -18, -22, -16), + ArrayBuffer(23, -1, 9, -1, 23), + ArrayBuffer(36, -18, 0, -18, 36), + ArrayBuffer(29, -67, -45, -67, 29), + ArrayBuffer(152, 74, 90, 74, 152) + ) + ) + } + + test("Should work when a single thread processes multiple filters (25pts)") { + val temp = new Problem2(imageSize, 1, 3) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array( + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Sharpen + ), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-10, -10, -9, -10, -10), + ArrayBuffer(11, 0, 3, 0, 11), + ArrayBuffer(18, -6, 0, -6, 18), + ArrayBuffer(17, -24, -15, -24, 17), + ArrayBuffer(86, 38, 45, 38, 86) + ) + ) + } + + test("Should work when multiple thread processes a single filter (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 1) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline(Array(temp.imageLib.Filter.Outline), rowsForThread(i))).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(5, 3, 3, 3, 5), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(5, 3, 3, 3, 5) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes two filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 2) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array(temp.imageLib.Filter.Outline, temp.imageLib.Filter.Sharpen), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(19, 7, 9, 7, 19), + ArrayBuffer(7, -6, -3, -6, 7), + ArrayBuffer(9, -3, 0, -3, 9), + ArrayBuffer(7, -6, -3, -6, 7), + ArrayBuffer(19, 7, 9, 7, 19) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(5, 3, 3, 3, 5), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(5, 3, 3, 3, 5) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes multiple same filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 4) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array( + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity + ), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes multiple different filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 4) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array( + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Sharpen, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Sharpen + ), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(-51, -31, -28, -31, -51), + ArrayBuffer(47, 2, 24, 2, 47), + ArrayBuffer(68, -24, 24, -24, 68), + ArrayBuffer(5, -154, -72, -154, 5), + ArrayBuffer(375, 83, 164, 83, 375) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(-10, -10, -9, -10, -10), + ArrayBuffer(11, 0, 3, 0, 11), + ArrayBuffer(18, -6, 0, -6, 18), + ArrayBuffer(17, -24, -15, -24, 17), + ArrayBuffer(86, 38, 45, 38, 86) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala new file mode 100644 index 0000000..645f9cb --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala @@ -0,0 +1,57 @@ +package concpar22final02.instrumentation + +trait MockedMonitor extends Monitor: + def scheduler: Scheduler + + // Can be overriden. + override def waitDefault() = + scheduler.log("wait") + scheduler.waitCount.incrementAndGet() + scheduler updateThreadState Wait(this, scheduler.threadLocks.tail) + override def synchronizedDefault[T](toExecute: => T): T = + scheduler.log("synchronized check") + val prevLocks = scheduler.threadLocks + scheduler updateThreadState Sync( + this, + prevLocks + ) // If this belongs to prevLocks, should just continue. + scheduler.log("synchronized -> enter") + try toExecute + finally + scheduler updateThreadState Running(prevLocks) + scheduler.log("synchronized -> out") + override def notifyDefault() = + scheduler mapOtherStates { state => + state match + case Wait(lockToAquire, locks) if lockToAquire == this => SyncUnique(this, state.locks) + case e => e + } + scheduler.notifyCount.incrementAndGet() + scheduler.log("notify") + override def notifyAllDefault() = + scheduler mapOtherStates { state => + state match + case Wait(lockToAquire, locks) if lockToAquire == this => Sync(this, state.locks) + case SyncUnique(lockToAquire, locks) if lockToAquire == this => Sync(this, state.locks) + case e => e + } + scheduler.notifyAllCount.incrementAndGet() + scheduler.log("notifyAll") + +abstract class ThreadState: + def locks: Seq[AnyRef] +trait CanContinueIfAcquiresLock extends ThreadState: + def lockToAquire: AnyRef +case object Start extends ThreadState: + def locks: Seq[AnyRef] = Seq.empty +case object End extends ThreadState: + def locks: Seq[AnyRef] = Seq.empty +case class Wait(lockToAquire: AnyRef, locks: Seq[AnyRef]) extends ThreadState +case class SyncUnique(lockToAquire: AnyRef, locks: Seq[AnyRef]) + extends ThreadState + with CanContinueIfAcquiresLock +case class Sync(lockToAquire: AnyRef, locks: Seq[AnyRef]) + extends ThreadState + with CanContinueIfAcquiresLock +case class Running(locks: Seq[AnyRef]) extends ThreadState +case class VariableReadWrite(locks: Seq[AnyRef]) extends ThreadState diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala new file mode 100644 index 0000000..a14587b --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala @@ -0,0 +1,20 @@ +package concpar22final02.instrumentation + +import scala.annotation.tailrec +import concpar22final02.* +import scala.collection.mutable.ArrayBuffer + +class SchedulableBarrier(val scheduler: Scheduler, size: Int) + extends Barrier(size) + with MockedMonitor + +class SchedulableProblem2( + val scheduler: Scheduler, + imageSize: Int, + threadCount: Int, + numFilters: Int +) extends Problem2(imageSize, threadCount, numFilters): + self => + + override val barrier = + ArrayBuffer.fill(numFilters)(SchedulableBarrier(scheduler, threadCount)) diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala new file mode 100644 index 0000000..4001ee3 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala @@ -0,0 +1,294 @@ +package concpar22final02.instrumentation + +import java.util.concurrent.* +import scala.concurrent.duration.* +import scala.collection.mutable.* +import Stats.* + +import java.util.concurrent.atomic.AtomicInteger + +sealed abstract class Result +case class RetVal(rets: List[Any]) extends Result +case class Except(msg: String, stackTrace: Array[StackTraceElement]) extends Result +case class Timeout(msg: String) extends Result + +/** A class that maintains schedule and a set of thread ids. The schedules are advanced after an + * operation of a SchedulableBuffer is performed. Note: the real schedule that is executed may + * deviate from the input schedule due to the adjustments that had to be made for locks + */ +class Scheduler(sched: List[Int]): + val maxOps = 500 // a limit on the maximum number of operations the code is allowed to perform + + var waitCount:AtomicInteger = new AtomicInteger(0) + var notifyCount:AtomicInteger = new AtomicInteger(0) + var notifyAllCount:AtomicInteger = new AtomicInteger(0) + + private var schedule = sched + var numThreads = 0 + private val realToFakeThreadId = Map[Long, Int]() + private val opLog = ListBuffer[String]() // a mutable list (used for efficient concat) + private val threadStates = Map[Int, ThreadState]() + + /** Runs a set of operations in parallel as per the schedule. Each operation may consist of many + * primitive operations like reads or writes to shared data structure each of which should be + * executed using the function `exec`. + * @timeout + * in milliseconds + * @return + * true - all threads completed on time, false -some tests timed out. + */ + def runInParallel(timeout: Long, ops: List[() => Any]): Result = + numThreads = ops.length + val threadRes = Array.fill(numThreads) { None: Any } + var exception: Option[(Throwable, Int)] = None + val syncObject = new Object() + var completed = new AtomicInteger(0) + // create threads + val threads = ops.zipWithIndex.map { case (op, i) => + new Thread( + new Runnable(): + def run(): Unit = + val fakeId = i + 1 + setThreadId(fakeId) + try + updateThreadState(Start) + val res = op() + updateThreadState(End) + threadRes(i) = res + // notify the main thread if all threads have completed + if completed.incrementAndGet() == ops.length then + syncObject.synchronized { syncObject.notifyAll() } + catch + case e: Throwable if exception != None => // do nothing here and silently fail + case e: Throwable => + log(s"throw ${e.toString}") + exception = Some((e, fakeId)) + syncObject.synchronized { syncObject.notifyAll() } + // println(s"$fakeId: ${e.toString}") + // Runtime.getRuntime().halt(0) //exit the JVM and all running threads (no other way to kill other threads) + ) + } + // start all threads + threads.foreach(_.start()) + // wait for all threads to complete, or for an exception to be thrown, or for the time out to expire + var remTime = timeout + syncObject.synchronized { + timed { + if completed.get() != ops.length then syncObject.wait(timeout) } { time => + remTime -= time + } + } + if exception.isDefined then + Except( + s"Thread ${exception.get._2} crashed on the following schedule: \n" + opLog.mkString("\n"), + exception.get._1.getStackTrace + ) + else if remTime <= 1 then // timeout ? using 1 instead of zero to allow for some errors + Timeout(opLog.mkString("\n")) + else + // every thing executed normally + RetVal(threadRes.toList) + + // Updates the state of the current thread + def updateThreadState(state: ThreadState): Unit = + val tid = threadId + synchronized { + threadStates(tid) = state + } + state match + case Sync(lockToAquire, locks) => + if locks.indexOf(lockToAquire) < 0 then waitForTurn + else + // Re-aqcuiring the same lock + updateThreadState(Running(lockToAquire +: locks)) + case Start => waitStart() + case End => removeFromSchedule(tid) + case Running(_) => + case _ => waitForTurn // Wait, SyncUnique, VariableReadWrite + + def waitStart(): Unit = + // while (threadStates.size < numThreads) { + // Thread.sleep(1) + // } + synchronized { + if threadStates.size < numThreads then wait() + else notifyAll() + } + + def threadLocks = + synchronized { + threadStates(threadId).locks + } + + def threadState = + synchronized { + threadStates(threadId) + } + + def mapOtherStates(f: ThreadState => ThreadState) = + val exception = threadId + synchronized { + for k <- threadStates.keys if k != exception do threadStates(k) = f(threadStates(k)) + } + + def log(str: String) = + if (realToFakeThreadId contains Thread.currentThread().getId()) then + val space = (" " * ((threadId - 1) * 2)) + val s = space + threadId + ":" + "\n".r.replaceAllIn(str, "\n" + space + " ") + opLog += s + + /** Executes a read or write operation to a global data structure as per the given schedule + * @param msg + * a message corresponding to the operation that will be logged + */ + def exec[T](primop: => T)(msg: => String, postMsg: => Option[T => String] = None): T = + if !(realToFakeThreadId contains Thread.currentThread().getId()) then primop + else + updateThreadState(VariableReadWrite(threadLocks)) + val m = msg + if m != "" then log(m) + if opLog.size > maxOps then + throw new Exception( + s"Total number of reads/writes performed by threads exceed $maxOps. A possible deadlock!" + ) + val res = primop + postMsg match + case Some(m) => log(m(res)) + case None => + res + + private def setThreadId(fakeId: Int) = synchronized { + realToFakeThreadId(Thread.currentThread.getId) = fakeId + } + + def threadId = + try realToFakeThreadId(Thread.currentThread().getId()) + catch + case e: NoSuchElementException => + throw new Exception( + "You are accessing shared variables in the constructor. This is not allowed. The variables are already initialized!" + ) + + private def isTurn(tid: Int) = synchronized { + (!schedule.isEmpty && schedule.head != tid) + } + + def canProceed(): Boolean = + val tid = threadId + canContinue match + case Some((i, state)) if i == tid => + // println(s"$tid: Runs ! Was in state $state") + canContinue = None + state match + case Sync(lockToAquire, locks) => updateThreadState(Running(lockToAquire +: locks)) + case SyncUnique(lockToAquire, locks) => + mapOtherStates { + _ match + case SyncUnique(lockToAquire2, locks2) if lockToAquire2 == lockToAquire => + Wait(lockToAquire2, locks2) + case e => e + } + updateThreadState(Running(lockToAquire +: locks)) + case VariableReadWrite(locks) => updateThreadState(Running(locks)) + true + case Some((i, state)) => + // println(s"$tid: not my turn but $i !") + false + case None => + false + + var threadPreference = + 0 // In the case the schedule is over, which thread should have the preference to execute. + + /** returns true if the thread can continue to execute, and false otherwise */ + def decide(): Option[(Int, ThreadState)] = + if !threadStates.isEmpty + then // The last thread who enters the decision loop takes the decision. + // println(s"$threadId: I'm taking a decision") + if threadStates.values.forall { + case e: Wait => true + case _ => false + } + then + val waiting = threadStates.keys.map(_.toString).mkString(", ") + val s = if threadStates.size > 1 then "s" else "" + val are = if threadStates.size > 1 then "are" else "is" + throw new Exception( + s"Deadlock: Thread$s $waiting $are waiting but all others have ended and cannot notify them." + ) + else + // Threads can be in Wait, Sync, SyncUnique, and VariableReadWrite mode. + // Let's determine which ones can continue. + val notFree = threadStates.collect { case (id, state) => state.locks }.flatten.toSet + val threadsNotBlocked = threadStates.toSeq.filter { + case (id, v: VariableReadWrite) => true + case (id, v: CanContinueIfAcquiresLock) => + !notFree(v.lockToAquire) || (v.locks contains v.lockToAquire) + case _ => false + } + if threadsNotBlocked.isEmpty then + val waiting = threadStates.keys.map(_.toString).mkString(", ") + val s = if threadStates.size > 1 then "s" else "" + val are = if threadStates.size > 1 then "are" else "is" + val whoHasLock = threadStates.toSeq.flatMap { case (id, state) => + state.locks.map(lock => (lock, id)) + }.toMap + val reason = threadStates + .collect { + case (id, state: CanContinueIfAcquiresLock) if !notFree(state.lockToAquire) => + s"Thread $id is waiting on lock ${state.lockToAquire} held by thread ${whoHasLock(state.lockToAquire)}" + } + .mkString("\n") + throw new Exception(s"Deadlock: Thread$s $waiting are interlocked. Indeed:\n$reason") + else if threadsNotBlocked.size == 1 + then // Do not consume the schedule if only one thread can execute. + Some(threadsNotBlocked(0)) + else + val next = + schedule.indexWhere(t => threadsNotBlocked.exists { case (id, state) => id == t }) + if next != -1 then + // println(s"$threadId: schedule is $schedule, next chosen is ${schedule(next)}") + val chosenOne = schedule(next) // TODO: Make schedule a mutable list. + schedule = schedule.take(next) ++ schedule.drop(next + 1) + Some((chosenOne, threadStates(chosenOne))) + else + threadPreference = (threadPreference + 1) % threadsNotBlocked.size + val chosenOne = threadsNotBlocked(threadPreference) // Maybe another strategy + Some(chosenOne) + // threadsNotBlocked.indexOf(threadId) >= 0 + /* + val tnb = threadsNotBlocked.map(_._1).mkString(",") + val s = if (schedule.isEmpty) "empty" else schedule.mkString(",") + val only = if (schedule.isEmpty) "" else " only" + throw new Exception(s"The schedule is $s but$only threads ${tnb} can continue")*/ + else canContinue + + /** This will be called before a schedulable operation begins. This should not use synchronized + */ + var numThreadsWaiting = new AtomicInteger(0) + // var waitingForDecision = Map[Int, Option[Int]]() // Mapping from thread ids to a number indicating who is going to make the choice. + var canContinue: Option[(Int, ThreadState)] = + None // The result of the decision thread Id of the thread authorized to continue. + private def waitForTurn = + synchronized { + if numThreadsWaiting.incrementAndGet() == threadStates.size then + canContinue = decide() + notifyAll() + // waitingForDecision(threadId) = Some(numThreadsWaiting) + // println(s"$threadId Entering waiting with ticket number $numThreadsWaiting/${waitingForDecision.size}") + while !canProceed() do wait() + } + numThreadsWaiting.decrementAndGet() + + /** To be invoked when a thread is about to complete + */ + private def removeFromSchedule(fakeid: Int) = synchronized { + // println(s"$fakeid: I'm taking a decision because I finished") + schedule = schedule.filterNot(_ == fakeid) + threadStates -= fakeid + if numThreadsWaiting.get() == threadStates.size then + canContinue = decide() + notifyAll() + } + + def getOperationLog() = opLog diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala new file mode 100644 index 0000000..c4bcda0 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala @@ -0,0 +1,127 @@ +package concpar22final02.instrumentation + +import scala.util.Random +import scala.collection.mutable.{Map as MutableMap} + +import Stats.* + +object TestHelper: + val noOfSchedules = 10000 // set this to 100k during deployment + val readWritesPerThread = 20 // maximum number of read/writes possible in one thread + val contextSwitchBound = 10 + val testTimeout = 240 // the total time out for a test in seconds + val schedTimeout = 15 // the total time out for execution of a schedule in secs + + // Helpers + /*def testManySchedules(op1: => Any): Unit = testManySchedules(List(() => op1)) + def testManySchedules(op1: => Any, op2: => Any): Unit = testManySchedules(List(() => op1, () => op2)) + def testManySchedules(op1: => Any, op2: => Any, op3: => Any): Unit = testManySchedules(List(() => op1, () => op2, () => op3)) + def testManySchedules(op1: => Any, op2: => Any, op3: => Any, op4: => Any): Unit = testManySchedules(List(() => op1, () => op2, () => op3, () => op4))*/ + + def testSequential[T](ops: Scheduler => Any)(assertions: T => (Boolean, String)) = + testManySchedules( + 1, + (sched: Scheduler) => + (List(() => ops(sched)), (res: List[Any]) => assertions(res.head.asInstanceOf[T])) + ) + + /** @numThreads + * number of threads + * @ops + * operations to be executed, one per thread + * @assertion + * as condition that will executed after all threads have completed (without exceptions) the + * arguments are the results of the threads + */ + def testManySchedules( + numThreads: Int, + ops: Scheduler => ( + List[() => Any], // Threads + List[Any] => (Boolean, String) + ) // Assertion + ) = + var timeout = testTimeout * 1000L + val threadIds = (1 to numThreads) + // (1 to scheduleLength).flatMap(_ => threadIds).toList.permutations.take(noOfSchedules).foreach { + val schedules = (new ScheduleGenerator(numThreads)).schedules() + var schedsExplored = 0 + schedules.takeWhile(_ => schedsExplored <= noOfSchedules && timeout > 0).foreach { + // case _ if timeout <= 0 => // break + case schedule => + schedsExplored += 1 + val schedr = new Scheduler(schedule) + // println("Exploring Sched: "+schedule) + val (threadOps, assertion) = ops(schedr) + if threadOps.size != numThreads then + throw new IllegalStateException( + s"Number of threads: $numThreads, do not match operations of threads: $threadOps" + ) + timed { schedr.runInParallel(schedTimeout * 1000, threadOps) } { t => timeout -= t } match + case Timeout(msg) => + throw new java.lang.AssertionError( + "assertion failed\n" + "The schedule took too long to complete. A possible deadlock! \n" + msg + ) + case Except(msg, stkTrace) => + val traceStr = + "Thread Stack trace: \n" + stkTrace.map(" at " + _.toString).mkString("\n") + throw new java.lang.AssertionError("assertion failed\n" + msg + "\n" + traceStr) + case RetVal(threadRes) => + // check the assertion + val (success, custom_msg) = assertion(threadRes) + if !success then + val msg = + "The following schedule resulted in wrong results: \n" + custom_msg + "\n" + schedr + .getOperationLog() + .mkString("\n") + throw new java.lang.AssertionError("Assertion failed: " + msg) + } + if timeout <= 0 then + throw new java.lang.AssertionError( + "Test took too long to complete! Cannot check all schedules as your code is too slow!" + ) + + /** A schedule generator that is based on the context bound + */ + class ScheduleGenerator(numThreads: Int): + val scheduleLength = readWritesPerThread * numThreads + val rands = (1 to scheduleLength).map(i => new Random(0xcafe * i)) // random numbers for choosing a thread at each position + def schedules(): LazyList[List[Int]] = + var contextSwitches = 0 + var contexts = List[Int]() // a stack of thread ids in the order of context-switches + val remainingOps = MutableMap[Int, Int]() + remainingOps ++= (1 to numThreads).map(i => + (i, readWritesPerThread) + ) // num ops remaining in each thread + val liveThreads = (1 to numThreads).toSeq.toBuffer + + /** Updates remainingOps and liveThreads once a thread is chosen for a position in the + * schedule + */ + def updateState(tid: Int): Unit = + val remOps = remainingOps(tid) + if remOps == 0 then liveThreads -= tid + else remainingOps += (tid -> (remOps - 1)) + val schedule = rands.foldLeft(List[Int]()) { + case (acc, r) if contextSwitches < contextSwitchBound => + val tid = liveThreads(r.nextInt(liveThreads.size)) + contexts match + case prev :: tail if prev != tid => // we have a new context switch here + contexts +:= tid + contextSwitches += 1 + case prev :: tail => + case _ => // init case + contexts +:= tid + updateState(tid) + acc :+ tid + case ( + acc, + _ + ) => // here context-bound has been reached so complete the schedule without any more context switches + if !contexts.isEmpty then contexts = contexts.dropWhile(remainingOps(_) == 0) + val tid = contexts match + case top :: tail => top + case _ => liveThreads(0) // here, there has to be threads that have not even started + updateState(tid) + acc :+ tid + } + schedule #:: schedules() diff --git a/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala new file mode 100644 index 0000000..5c76ec9 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala @@ -0,0 +1,14 @@ +package concpar22final02.instrumentation + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.concurrent.ExecutionContext.Implicits.global + +object TestUtils: + def failsOrTimesOut[T](action: => T): Boolean = + val asyncAction = Future { + action + } + try Await.result(asyncAction, 2000.millisecond) + catch case _: Throwable => return true + return false diff --git a/previous-exams/2022-final-solutions/concpar22final03/.gitignore b/previous-exams/2022-final-solutions/concpar22final03/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final-solutions/concpar22final03/assignment.sbt b/previous-exams/2022-final-solutions/concpar22final03/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final-solutions/concpar22final03/build.sbt b/previous-exams/2022-final-solutions/concpar22final03/build.sbt new file mode 100644 index 0000000..19eefd4 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final03" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/CourseraStudent.scala b/previous-exams/2022-final-solutions/concpar22final03/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/MOOCSettings.scala b/previous-exams/2022-final-solutions/concpar22final03/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/StudentTasks.scala b/previous-exams/2022-final-solutions/concpar22final03/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/build.properties b/previous-exams/2022-final-solutions/concpar22final03/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/buildSettings.sbt b/previous-exams/2022-final-solutions/concpar22final03/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final-solutions/concpar22final03/project/plugins.sbt b/previous-exams/2022-final-solutions/concpar22final03/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Economics.scala b/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Economics.scala new file mode 100644 index 0000000..c032714 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Economics.scala @@ -0,0 +1,44 @@ +package concpar22final03 + +import scala.concurrent.Future + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.util.Random + +trait Economics: + + /** A trading card from the game Scala: The Programming. We can own a card, but once don't + * anymore. + */ + final class Card(val name: String) + def isMine(c: Card): Boolean + + /** This function uses the best available database to return the sell value of a card on the + * market. + */ + def valueOf(cardName: String): Int = List(1, cardName.length).max + + /** This method represents an exact amount of money that can be hold, spent, or put in the bank + */ + final class MoneyBag() + def moneyIn(m: MoneyBag): Int + + /** If you sell a card, at some point in the future you will get some money (in a bag). + */ + def sellCard(c: Card): Future[MoneyBag] + + /** You can buy any "Scala: The Programming" card by providing a bag of money with the appropriate + * amount and waiting for the transaction to take place. You will own the returned card. + */ + def buyCard(money: MoneyBag, name: String): Future[Card] + + /** This simple bank account holds money for you. You can bring a money bag to increase your + * account's balance, or withdraw a money bag of any size not greater than your account's + * balance. + */ + def balance: Int + def withdraw(amount: Int): Future[MoneyBag] + def deposit(bag: MoneyBag): Future[Unit] + + class NotEnoughMoneyException extends Exception("Not enough money provided to buy those cards") \ No newline at end of file diff --git a/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Problem3.scala b/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Problem3.scala new file mode 100644 index 0000000..3b7fa1b --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/src/main/scala/concpar22final03/Problem3.scala @@ -0,0 +1,52 @@ +package concpar22final03 + +import scala.concurrent.Future +import concurrent.ExecutionContext.Implicits.global + +trait Problem3: + val economics: Economics + import economics.* + + /** The objective is to propose a service of deck building. People come to you with some money and + * some cards they want to sell, and you need to return them a complete deck of the cards they + * want. + */ + def orderDeck( + bag: MoneyBag, + cardsToSell: List[Card], + wantedDeck: List[String] + ): Future[List[Card]] = + + Future { + val totalGivenMoney = + cardsToSell.foldLeft(moneyIn(bag))((sum, c) => sum + valueOf(c.name)) + val totalNeededMoney = + wantedDeck.foldLeft(0)((sum, n) => sum + valueOf(n)) + if totalGivenMoney < totalNeededMoney then + throw new NotEnoughMoneyException() + val soldCards: Future[Unit] = + if moneyIn(bag) != 0 then sellListOfCards(cardsToSell).zip(deposit(bag)).map(_ => ()) + else sellListOfCards(cardsToSell).map(_ => ()) + soldCards.flatMap { _ => buyListOfCards(wantedDeck) } + }.flatten + + /** This helper function will sell the provided list of cards and put the money on your personal + * bank account. It returns a Future of Unit, which indicates when all sales are completed. + */ + def sellListOfCards(cardsToSell: List[Card]): Future[Unit] = + val moneyFromSales: List[Future[Unit]] = cardsToSell.map { c => + sellCard(c).flatMap(m => deposit(m).map { _ => }) + } + Future + .sequence(moneyFromSales) + .map(_ => ()) // Future.sequence transforms a List[Future[A]] into a Future[List[A]] + + /** This helper function, given a list of wanted card names and assuming there is enough money in + * the bank account, will buy (in the future) those cards, and return them. + */ + def buyListOfCards(wantedDeck: List[String]): Future[List[Card]] = + + val boughtCards: List[Future[Card]] = wantedDeck.map { name => + withdraw(valueOf(name)).flatMap(mb => buyCard(mb, name)) + } + Future.sequence(boughtCards) diff --git a/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala b/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala new file mode 100644 index 0000000..a8c327e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala @@ -0,0 +1,98 @@ +package concpar22final03 + +import scala.concurrent.Future + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.util.Random + +trait EconomicsTest extends Economics: + val ownedCards: collection.mutable.Set[Card] = collection.mutable.Set[Card]() + def owned(c: Card): Boolean = ownedCards(c) + def isMine(c: Card): Boolean = ownedCards(c) + + override def valueOf(cardName: String): Int = List(1, cardName.length).max + + /** This is a container for an exact amount of money that can be hold, spent, or put in the bank + */ + val moneyInMoneyBag = collection.mutable.Map[MoneyBag, Int]() + def moneyIn(m: MoneyBag): Int = moneyInMoneyBag.getOrElse(m, 0) + + /** If you sell a card, at some point in the future you will get some money (in a bag). + */ + def sellCard(c: Card): Future[MoneyBag] = + Future { + Thread.sleep(sellWaitTime()) + synchronized( + if owned(c) then + ownedCards.remove(c) + getMoneyBag(valueOf(c.name)) + else + throw Exception( + "This card doesn't belong to you or has already been sold, you can't sell it." + ) + ) + } + + /** You can buy any "Scala: The Programming" card by providing a bag of money with the appropriate + * amount and waiting for the transaction to take place + */ + def buyCard(bag: MoneyBag, name: String): Future[Card] = + Future { + Thread.sleep(buyWaitTime()) + synchronized { + if moneyIn(bag) != valueOf(name) then + throw Exception( + "You didn't provide the exact amount of money necessary to buy this card." + ) + else moneyInMoneyBag.update(bag, 0) + getCard(name) + } + + } + + /** This simple bank account hold money for you. You can bring a money bag to increase your + * account, or withdraw a money bag of any size not greater than your account's balance. + */ + private var balance_ = initialBalance() + def balance: Int = balance_ + def withdraw(amount: Int): Future[MoneyBag] = + Future { + Thread.sleep(withdrawWaitTime()) + synchronized( + if balance_ >= amount then + balance_ -= amount + getMoneyBag(amount) + else + throw new Exception( + "You try to withdraw more money than you have on your account" + ) + ) + } + + def deposit(bag: MoneyBag): Future[Unit] = + Future { + Thread.sleep(depositWaitTime()) + synchronized { + if moneyInMoneyBag(bag) == 0 then throw new Exception("You are depositing en empty bag!") + else + balance_ += moneyIn(bag) + moneyInMoneyBag.update(bag, 0) + } + } + + def sellWaitTime(): Int + def buyWaitTime(): Int + def withdrawWaitTime(): Int + def depositWaitTime(): Int + def initialBalance(): Int + + def getMoneyBag(i: Int) = + val m = MoneyBag() + synchronized(moneyInMoneyBag.update(m, i)) + m + + def getCard(n: String): Card = + val c = Card(n) + synchronized(ownedCards.update(c, true)) + c diff --git a/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala b/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala new file mode 100644 index 0000000..a99852a --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala @@ -0,0 +1,201 @@ +package concpar22final03 + +import scala.concurrent.duration.* +import scala.concurrent.{Await, Future} +import scala.util.{Try, Success, Failure} +import scala.concurrent.ExecutionContext.Implicits.global + +class Problem3Suite extends munit.FunSuite: + trait Prob3Test extends Problem3: + override val economics: EconomicsTest + class Test1 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = 10 + override def buyWaitTime() = 20 + override def depositWaitTime() = 30 + override def withdrawWaitTime() = 40 + override def initialBalance() = 0 + class Test2 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = 100 + override def buyWaitTime() = 5 + override def depositWaitTime() = 50 + override def withdrawWaitTime() = 5 + override def initialBalance() = 0 + + class Test3 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + val rgen = new scala.util.Random(666) + override def sellWaitTime() = rgen.nextInt(100) + override def buyWaitTime() = rgen.nextInt(100) + override def depositWaitTime() = rgen.nextInt(100) + override def withdrawWaitTime() = rgen.nextInt(100) + override def initialBalance() = 0 + + class Test4 extends Prob3Test: + var counter = 5 + def next(): Int = + counter = counter + 5 % 119 + counter + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = next() + override def buyWaitTime() = next() + override def depositWaitTime() = next() + override def withdrawWaitTime() = next() + override def initialBalance() = next() + + def testCases = List(new Test1, new Test2) + def unevenTestCases = List(new Test3, new Test4) + + def tot(cards: List[String]): Int = + cards.map[Int]((n: String) => n.length).sum + + def testOk( + t: Prob3Test, + money: Int, + sold: List[String], + wanted: List[String] + ): Unit = + import t.* + import economics.* + val f = orderDeck(getMoneyBag(money), sold.map(getCard), wanted) + val r = Await.ready(f, 3.seconds).value.get + assert(r.isSuccess) + r match + case Success(d) => + assertEquals(d.map(_.name).sorted, wanted.sorted) + assertEquals(d.length, wanted.length) + assertEquals(isMine(d.head), true) + case Failure(e) => () + + def testFailure( + t: Prob3Test, + money: Int, + sold: List[String], + wanted: List[String] + ): Unit = + import t.* + import economics.* + val f = orderDeck(getMoneyBag(money), sold.map(getCard), wanted) + val r = Await.ready(f, 3.seconds).value.get + assert(r.isFailure) + r match + case Failure(e: NotEnoughMoneyException) => () + case _ => fail("Should have thrown a NotEnoughMoneyException exception, but did not") + + // --- Without sold cards --- + + test( + "Should work correctly when a single card is asked with enough money (no card sold) (20pts)" + ) { + testCases.foreach(t => testOk(t, 7, Nil, List("Tefeiri"))) + } + test( + "Should work correctly when a single card is asked with enough money (no card sold, uneven waiting time) (10pts)" + ) { + unevenTestCases.foreach(t => testOk(t, 7, Nil, List("Tefeiri"))) + } + test( + "Should work correctly when multiple cards are asked with enough money (no card sold) (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + testCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when multiple cards are asked with enough money (no card sold, uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + unevenTestCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when asked duplicates of cards, with enough money (no card sold) (20pts)" + ) { + val cards = List("aaaa", "aaaa", "aaaa", "dd", "dd", "dd", "dd") + testCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when asked duplicates of cards, with enough money (no card sold, uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "aaaa", "aaaa", "dd", "dd", "dd", "dd") + unevenTestCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + + // --- With sold cards --- + + test( + "Should work correctly when a single card is bought and a single of the same price is sold (20pts)" + ) { + testCases.foreach(t => testOk(t, 0, List("Chandra"), List("Tefeiri"))) + } + test( + "Should work correctly when a single card is bought and a single of the same price is sold (uneven waiting time) (10pts)" + ) { + unevenTestCases.foreach(t => testOk(t, 0, List("Chandra"), List("Tefeiri"))) + } + + test( + "Should work correctly when multiple cards are asked and multiple of matching values are sold (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("1111111", "2", "3333", "44", "55555", "666", "7777") + testCases.foreach(t => testOk(t, 0, sold, cards)) + } + test( + "Should work correctly when multiple cards are asked and multiple of matching values are sold (uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("1111111", "2", "3333", "44", "55555", "666", "7777") + unevenTestCases.foreach(t => testOk(t, 0, sold, cards)) + } + test( + "Should work correctly when multiple cards are asked and multiple of the same total value are sold (20pts)" + ) { + val cards2 = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold2 = List("111111111", "22", "3", "44", "555555", "666", "777") + assert(tot(sold2) == tot(cards2)) + testCases.foreach(t => testOk(t, 0, sold2, cards2)) + } + test( + "Should work correctly when multiple cards are asked and multiple of the same total value are sold (uneven waiting time) (10pts)" + ) { + val cards2 = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold2 = List("111111111", "22", "3", "44", "555555", "666", "777") + assert(tot(sold2) == tot(cards2)) + unevenTestCases.foreach(t => testOk(t, 0, sold2, cards2)) + } + + test( + "Should work correctly when given money and sold cards are sufficient for the wanted cards (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + testCases.foreach(t => testOk(t, bagMoney, sold, cards)) + } + test( + "Should work correctly when given money and sold cards are sufficient for the wanted cards (uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + unevenTestCases.foreach(t => testOk(t, bagMoney, sold, cards)) + } + + // --- Failures --- + + test( + "Should return a failure when too little money is provided (no card sold) (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + testCases.foreach(t => testFailure(t, tot(cards) - 1, Nil, cards)) + testCases.foreach(t => testFailure(t, tot(cards) - 50, Nil, cards)) + } + + test( + "Should return a failure when too little money or sold cards are provided (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + testCases.foreach(t => testFailure(t, bagMoney - 2, sold, cards)) + } diff --git a/previous-exams/2022-final-solutions/concpar22final04/.gitignore b/previous-exams/2022-final-solutions/concpar22final04/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final-solutions/concpar22final04/assignment.sbt b/previous-exams/2022-final-solutions/concpar22final04/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final-solutions/concpar22final04/build.sbt b/previous-exams/2022-final-solutions/concpar22final04/build.sbt new file mode 100644 index 0000000..ea5fb5d --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/build.sbt @@ -0,0 +1,23 @@ +course := "concpar" +assignment := "concpar22final04" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val akkaVersion = "2.6.19" +val logbackVersion = "1.2.11" +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-testkit" % akkaVersion, + // SLF4J backend + // See https://doc.akka.io/docs/akka/current/typed/logging.html#slf4j-backend + "ch.qos.logback" % "logback-classic" % logbackVersion +) +fork := true +javaOptions ++= Seq("-Dakka.loglevel=Error", "-Dakka.actor.debug.receive=on") + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/CourseraStudent.scala b/previous-exams/2022-final-solutions/concpar22final04/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/MOOCSettings.scala b/previous-exams/2022-final-solutions/concpar22final04/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/StudentTasks.scala b/previous-exams/2022-final-solutions/concpar22final04/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/build.properties b/previous-exams/2022-final-solutions/concpar22final04/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/buildSettings.sbt b/previous-exams/2022-final-solutions/concpar22final04/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final-solutions/concpar22final04/project/plugins.sbt b/previous-exams/2022-final-solutions/concpar22final04/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final-solutions/concpar22final04/src/main/scala/concpar22final04/Problem4.scala b/previous-exams/2022-final-solutions/concpar22final04/src/main/scala/concpar22final04/Problem4.scala new file mode 100644 index 0000000..991be1c --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/src/main/scala/concpar22final04/Problem4.scala @@ -0,0 +1,220 @@ +package concpar22final04 + +import akka.actor.* +import akka.testkit.* +import java.util.Date +import akka.event.LoggingReceive +import akka.pattern.* +import akka.util.Timeout +import concurrent.duration.* +import scala.concurrent.Future +import scala.concurrent.ExecutionContext + +given timeout: Timeout = Timeout(200.millis) + +/** Data associated with a song: a unique `id`, a `title` and an `artist`. + */ +case class Song(id: Int, title: String, artist: String) + +/** An activity in a user's activity feed, representing that `userRef` is + * listening to `songId`. + */ +case class Activity(userId: String, userName: String, songId: Int) + +/** Companion object of the `User` class. + */ +object User: + /** Messages that can be sent to User actors. + */ + enum Protocol: + /** Asks for a user name and id. Should be answered by a Response.Info. + */ + case GetInfo + + /** Asks home page data. Should be answered by a Response.HomepageData. + */ + case GetHomepageData + + /** Like song with id `songId`. + */ + case Like(songId: Int) + + /** Unlike song with id `songId`. + */ + case Unlike(songId: Int) + + /** Adds `subscriber` to the list of subscribers. + */ + case Subscribe(subscriber: ActorRef) + + /** Remove `subscriber` from the list of subscribers. + */ + case Unsubscribe(subscriber: ActorRef) + + /** Adds the activity `activity` to the activity feed. This message will be + * sent by the users this user has subscribed to. + */ + case AddActivity(activity: Activity) + + /** Sent when a user starts playing a song with id `songId`. The recipient + * should notify all its subscribers to update their activity feeds by + * sending them `AddActivity(Activity(...))` messages. No answer is + * expected. This message is sent by external actors. + */ + case Play(songId: Int) + + /** Asks for home page text. Should be answered by a Response.HomepageText. + */ + case GetHomepageText + + /** Responses that can be sent back from User actors. + */ + enum Responses: + /** Answer to a Protocol.GetInfo message + */ + case Info(id: String, name: String) + + /** Answer to a Protocol.GetHomepageData message + */ + case HomepageData(songIds: List[Int], activities: List[Activity]) + + /** Answer to a Protocol.GetHomepageText message + */ + case HomepageText(result: String) + +/** The `User` actor, responsible to handle `User.Protocol` messages. + */ +class User(id: String, name: String, songsStore: ActorRef) extends Actor: + import User.* + import User.Protocol.* + import User.Responses.* + import SongsStore.Protocol.* + import SongsStore.Responses.* + + given ExecutionContext = context.system.dispatcher + + /** Liked songs, by reverse date of liking time (the last liked song must + * be the first must be the first element of the list). Elements of this + * list must be unique: a song can only be liked once. Liking a song + * twice should not change the order. + */ + var likedSongs: List[Int] = List() + + /** Users who have subscribed to this users. + */ + var subscribers: Set[ActorRef] = Set() + + /** Activity feed, by reverse date of activity time (the last added + * activity must be the first element of the list). Items in this list + * should be unique by `userRef`. If a new activity with a `userRef` + * already in the list is added, the former should be removed, so that we + * always see the latest activity for each user we have subscribed to. + */ + var activityFeed: List[Activity] = List() + + + /** This actor's behavior. */ + + override def receive: Receive = LoggingReceive { + case GetInfo => + sender() ! Info(id, name) + case GetHomepageData => + sender() ! HomepageData(likedSongs, activityFeed) + case Like(songId) if !likedSongs.contains(songId) => + likedSongs = songId :: likedSongs + case Unlike(songId) => + likedSongs = likedSongs.filter(_ != songId) + case Subscribe(ref: ActorRef) => + subscribers = subscribers + ref + case Unsubscribe(ref: ActorRef) => + subscribers = subscribers - ref + case AddActivity(activity: Activity) => + activityFeed = activity :: activityFeed.filter(_.userId != activity.userId) + case Play(songId) => + subscribers.foreach(_ ! AddActivity(Activity(id, name, songId))) + case GetHomepageText => + val likedSongsFuture: Future[Songs] = + (songsStore ? GetSongs(likedSongs)).mapTo[Songs] + val activitySongsFuture: Future[Songs] = + (songsStore ? GetSongs(activityFeed.map(_.songId))).mapTo[Songs] + val response: Future[HomepageText] = + for + likedSongs <- likedSongsFuture; + activitySongs <- activitySongsFuture + yield HomepageText(f""" + |Howdy ${name}! + | + |Liked Songs: + |${likedSongs.songs + .map(song => f"* ${song.title} by ${song.artist}") + .mkString("\n")} + | + |Activity Feed: + |${activityFeed + .zip(activitySongs.songs) + .map((activity, song) => f"* ${activity.userName} is listening to ${song.title} by ${song.artist}") + .mkString("\n")}""".stripMargin.trim) + response.pipeTo(sender()) + } + +/** Objects containing the messages a songs store should handle. + */ +object SongsStore: + /** Ask information about a list of songs by their ids. + */ + enum Protocol: + case GetSongs(songIds: List[Int]) + + /** List of `Song` corresponding to the list of IDs given to `GetSongs`. + */ + enum Responses: + case Songs(songs: List[Song]) + +/** A mock implementation of a songs store. + */ +class MockSongsStore extends Actor: + import SongsStore.Protocol.* + import SongsStore.Responses.* + import SongsStore.* + + val songsDB = Map( + 1 -> Song(1, "High Hopes", "Pink Floyd"), + 2 -> Song(2, "Sunny", "Boney M."), + 3 -> Song(3, "J'irai où tu iras", "Céline Dion & Jean-Jacques Goldman"), + 4 -> Song(4, "Ce monde est cruel", "Vald"), + 5 -> Song(5, "Strobe", "deadmau5"), + 6 -> Song(6, "Désenchantée", "Mylène Farmer"), + 7 -> Song(7, "Straight Edge", "Minor Threat"), + 8 -> Song(8, "Hold the line", "TOTO"), + 9 -> Song(9, "Anarchy in the UK", "Sex Pistols"), + 10 -> Song(10, "Breakfast in America", "Supertramp") + ) + + override def receive: Receive = LoggingReceive { case GetSongs(songsIds) => + sender() ! Songs(songsIds.map(songsDB)) + } + +///////////////////////// +// DEBUG // +///////////////////////// + +/** Infrastructure to help debugging. In sbt use `run` to execute this code. + * The TestKit is an actor that can send messages and check the messages it receives (or not). + */ +@main def debug() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender: + import User.* + import User.Protocol.* + import User.Responses.* + import SongsStore.Protocol.* + import SongsStore.Responses.* + + try + val songsStore = system.actorOf(Props(MockSongsStore()), "songsStore") + val anita = system.actorOf(Props(User("100", "Anita", songsStore))) + + anita ! Like(6) + expectNoMessage() // expects no message is received + + anita ! GetHomepageData + expectMsg(HomepageData(List(6), List())) + finally shutdown(system) diff --git a/previous-exams/2022-final-solutions/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala b/previous-exams/2022-final-solutions/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala new file mode 100644 index 0000000..f88d129 --- /dev/null +++ b/previous-exams/2022-final-solutions/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala @@ -0,0 +1,361 @@ +package concpar22final04 + +import akka.actor.* +import akka.testkit.* +import akka.pattern.* +import akka.util.Timeout +import concurrent.duration.* +import User.Protocol.* +import User.Responses.* +import SongsStore.Protocol.* +import SongsStore.Responses.* +import scala.util.{Try, Success, Failure} +import com.typesafe.config.ConfigFactory +import java.util.Date +import scala.util.Random + +class Problem4Suite extends munit.FunSuite: +//--- + Random.setSeed(42178263) +/*+++ + Random.setSeed(42) +++*/ + + test("after receiving GetInfo, should answer with Info (20pts)") { + new MyTestKit: + def tests() = + ada ! GetInfo + expectMsg(Info("1", "Ada")) + } + + test("after receiving GetHomepageData, should answer with the correct HomepageData when there is no liked songs and no activity items (30pts)") { + new MyTestKit: + def tests() = + ada ! GetHomepageData + expectMsg(HomepageData(List(), List())) + } + + test("after receiving Like(1), should add 1 to the list of liked songs (20pts)") { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(1), List())) + } + + test( + "after receiving Like(1) and then Like(2), the list of liked songs should start with List(2, 1) (20pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Like(1) and then Like(1), song 1 should be in the list of liked songs only once (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(1), List())) + } + + test( + "after receiving Like(1), Like(2) and then Like(1), the list of liked songs should start with List(2, 1) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Like(1), Unlike(1) and then Unlike(1), the list of liked songs should not contain song 1 (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Subscribe(aUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser (20pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + } + + test( + "after receiving Subscribe(aUser), Subscribe(bUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + val donald = new TestProbe(system) + ada ! Subscribe(donald.ref) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + donald.expectMsg(AddActivity(Activity("1", "Ada", 5))) + } + + test( + "after receiving Subscribe(aUser), Subscribe(aUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser only once (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + expectNoMessage() + } + + test( + "after receiving Subscribe(aUser), Unsubscribe(aUser) and then Play(5), should not send AddActivity(Activity(\"1\", 5)) to aUser (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + ada ! Unsubscribe(self) + expectNoMessage() + ada ! Play(5) + expectNoMessage() + } + + test( + "after receiving AddActivity(Activity(\"1\", 5)), Activity(\"1\", 5) should be in the activity feed (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! AddActivity(Activity("0", "Self", 5)) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(), List(Activity("0", "Self", 5)))) + } + + test( + "after receiving AddActivity(Activity(\"1\", 5)) and AddActivity(Activity(\"1\", 6)), Activity(\"1\", 6) should be in the activity feed and Activity(\"1\", 5) should not (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! AddActivity(Activity("0", "Self", 5)) + expectNoMessage() + ada ! AddActivity(Activity("0", "Self", 6)) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(), List(Activity("0", "Self", 6)))) + } + + test( + "after receiving GetHomepageText, should answer with a result containing \"Howdy $name!\" where $name is the user's name (10pts)" + ) { + new MyTestKit: + def tests() = + val name = Random.alphanumeric.take(5).mkString + val randomUser = system.actorOf(Props(classOf[User], "5", name, songsStore), "user-5") + randomUser ! GetHomepageText + expectMsgClass(classOf[HomepageText]).result.contains(f"Howdy $name!") + } + + test( + "after receiving GetHomepageText, should answer with the correct names of liked songs (1) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(8) + expectNoMessage() + ada ! Like(3) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(2) + .take(4) + .mkString("\n") + .trim, + """ + |Liked Songs: + |* Sunny by Boney M. + |* J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + |* Hold the line by TOTO + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct names of liked songs (2) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(9) + expectNoMessage() + ada ! Like(7) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(2) + .take(3) + .mkString("\n") + .trim, + """ + |Liked Songs: + |* Straight Edge by Minor Threat + |* Anarchy in the UK by Sex Pistols + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct activity feed (1) (10pts)" + ) { + new MyTestKit: + def tests() = + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + bob ! Play(3) + expectNoMessage() + carol ! Play(8) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(4) + .take(10) + .mkString("\n") + .trim, + """ + |Activity Feed: + |* Carol is listening to Hold the line by TOTO + |* Bob is listening to J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct activity feed (2) (10pts)" + ) { + new MyTestKit: + def tests() = + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + bob ! Play(9) + expectNoMessage() + carol ! Play(10) + expectNoMessage() + donald ! Play(6) + expectNoMessage() + bob ! Play(7) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(4) + .take(10) + .mkString("\n") + .trim, + """ + |Activity Feed: + |* Bob is listening to Straight Edge by Minor Threat + |* Donald is listening to Désenchantée by Mylène Farmer + |* Carol is listening to Breakfast in America by Supertramp + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct text (full test) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + donald ! Play(3) + expectNoMessage() + bob ! Play(4) + expectNoMessage() + carol ! Play(5) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .mkString("\n") + .trim, + """ + |Howdy Ada! + | + |Liked Songs: + |* Sunny by Boney M. + |* High Hopes by Pink Floyd + | + |Activity Feed: + |* Carol is listening to Strobe by deadmau5 + |* Bob is listening to Ce monde est cruel by Vald + |* Donald is listening to J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + """.stripMargin.trim + ) + } + + abstract class MyTestKit + extends TestKit(ActorSystem("TestSystem")) + with ImplicitSender: + val songsStore = system.actorOf(Props(MockSongsStore()), "songsStore") + def makeAda() = system.actorOf(Props(classOf[User], "1", "Ada", songsStore), "user-1") + val ada = makeAda() + val bob = system.actorOf(Props(classOf[User], "2", "Bob", songsStore), "user-2") + val carol = system.actorOf(Props(classOf[User], "3", "Carol", songsStore), "user-3") + val donald = system.actorOf(Props(classOf[User], "4", "Donald", songsStore), "user-4") + def tests(): Unit + try tests() + finally shutdown(system) diff --git a/previous-exams/2022-final/concpar22final01.md b/previous-exams/2022-final/concpar22final01.md new file mode 100644 index 0000000..7b8d10e --- /dev/null +++ b/previous-exams/2022-final/concpar22final01.md @@ -0,0 +1,102 @@ +# Problem 1: Combiners + +## Setup + +Use the following commands to make a fresh clone of your repository: + +``` +git clone -b concpar22final01 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar22final01 +``` + +If you have issues with the IDE, try [reimporting the +build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), +if you still have problems, use `compile` in sbt instead. + +## Exercise + +In this exercise, you will implement an array Combiner. The Combiner internally uses a double linked list whose nodes also point to their successor's successor and their predecessor's predecessor. Your goal is to complete the implementation of the (simplified) Combiner interface, by implementing the `result` method to compute the result array from this array combiner. + +Here you can see the declaration of the `DLLCombiner` class and the related `Node` class definition. Look at the `Lib` trait in the `lib.scala` file to find all definitions of relevant functions and classes. + +```scala + class Node(val value: Int): + protected var next: Node = null // null for last node. + protected var next2: Node = null // null for last node. + protected var previous: Node = null // null for first node. + protected var previous2: Node = null // null for first node. + + def getNext: Node = next // do NOT use in the result method + def getNext2: Node = next2 + def getPrevious: Node = previous // do NOT use in the result method + def getPrevious2: Node = previous2 + + def setNext(n: Node): Unit = next = n + def setNext2(n: Node): Unit = next2 = n + def setPrevious(n: Node): Unit = previous = n + def setPrevious2(n: Node): Unit = previous2 = n + + + // Simplified Combiner interface + // Implements methods `+=` and `combine` + // Abstract methods should be implemented in subclasses + abstract class DLLCombiner +``` + +`DLLCombiner` class contains the implementation of methods `+=` and `combine`. You should look at them to better understand the structure of this array Combiner, before moving on to solving this exercise. + +Your task in the exercise will be to implement the `result` method of the `DLLCombinerImplementation` class. This method should compute the result array from this array combiner. In your solution, you should **not** use methods `getNext` and `getPrevious`, but only `getNext2` and `getPrevious2`, to reduce the number of moving operations. + +According to the Combiner contract, `result` should work in parallel. Implement this method efficiently using 4 parallel tasks, by copying the double linked list to the array from both ends at the same time. Two threads should start from the start of the list and two from the end. In each case, one thread would be responsible for odd indexes and the other for even ones. + +Following the description above, your task in the exercise is to: + + 1. Implement the four tasks to copy parts of the resulting array. Each task is responsible for copying one quarter of the array: + - `task1` copies every other Integer element of data array, starting from the first (index 0), up to the middle + - `task2` copies every other Integer element of data array, starting from the second, up to the middle + - `task3` copies every other Integer element of data array, starting from the second to last, up to the middle + - `task4` copies every other Integer element of data array, starting from the last, up to the middle + 2. Implement the method `result` to compute the result array in parallel using those four tasks. + + Here is one example of the `result` method: + +```scala + val combiner1 = new DLLCombinerImplementation + combiner1 += 7 + combiner1 += 2 + combiner1 += 4 + combiner1 += 3 + combiner1 += 9 + combiner1 += 5 + combiner1 += 1 + + val res1 = combiner1.result() // (7, 2, 4, 3, 9, 5, 1) +``` +In this example, `task1` was responsible for copying elements at indexes 0 and 2, `task2` for copying the element at index 1, `task3` for copying elements at indexes 5 and 3, and `task4` for copying element at indexes 6 and 4. + +Here is another example with combining: + +```scala + val c1 = new DLLCombinerImplementation + c1 += 7 + c1 += 2 + c1 += 4 + c1 += 3 + c1 += 9 + c1 += 5 + c1 += 1 + + val c2 = new DLLCombinerImplementation + c2 += 6 + c2 += 8 + c2 += 5 + c2 += 1 + + val c3 = new DLLCombinerImplementation + c3 += 1 + + c1.combine(c2).combine(c3) + val res = c1.result() // (7, 2, 4, 3, 9, 5, 1, 6, 8, 5, 1, 1) +``` + +You can get partial points for solving parts of this exercise. +In your solution you should only make changes to the `DLLCombinerImplementation` class. You are not allowed to change the file `lib.scala`. diff --git a/previous-exams/2022-final/concpar22final01/.gitignore b/previous-exams/2022-final/concpar22final01/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final/concpar22final01/assignment.sbt b/previous-exams/2022-final/concpar22final01/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final/concpar22final01/build.sbt b/previous-exams/2022-final/concpar22final01/build.sbt new file mode 100644 index 0000000..beb0e5c --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final01" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final/concpar22final01/project/CourseraStudent.scala b/previous-exams/2022-final/concpar22final01/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final/concpar22final01/project/MOOCSettings.scala b/previous-exams/2022-final/concpar22final01/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final/concpar22final01/project/StudentTasks.scala b/previous-exams/2022-final/concpar22final01/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final/concpar22final01/project/build.properties b/previous-exams/2022-final/concpar22final01/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final/concpar22final01/project/buildSettings.sbt b/previous-exams/2022-final/concpar22final01/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final/concpar22final01/project/plugins.sbt b/previous-exams/2022-final/concpar22final01/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/Problem1.scala b/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/Problem1.scala new file mode 100644 index 0000000..70e569c --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/Problem1.scala @@ -0,0 +1,31 @@ +package concpar22final01 + +trait Problem1 extends Lib: + + class DLLCombinerImplementation extends DLLCombiner: + + // Copies every other Integer element of data array, starting from the first (index 0), up to the middle + def task1(data: Array[Int]) = task { + ??? + } + + // Copies every other Integer element of data array, starting from the second, up to the middle + def task2(data: Array[Int]) = task { + ??? + } + + // Copies every other Integer element of data array, starting from the second to last, up to the middle + def task3(data: Array[Int]) = task { + ??? + } + + // Copies every other Integer element of data array, starting from the last, up to the middle + // This is executed on the current thread. + def task4(data: Array[Int]) = + ??? + + def result(): Array[Int] = + val data = new Array[Int](size) + ??? + + data diff --git a/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/lib.scala b/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/lib.scala new file mode 100644 index 0000000..6d9d6ee --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/src/main/scala/concpar22final01/lib.scala @@ -0,0 +1,81 @@ +package concpar22final01 + +import java.util.concurrent.* +import scala.util.DynamicVariable + +trait Lib: + class Node(val value: Int): + protected var next: Node = null // null for last node. + protected var next2: Node = null // null for last node. + protected var previous: Node = null // null for first node. + protected var previous2: Node = null // null for first node. + + def getNext: Node = next // do NOT use in the result method + def getNext2: Node = next2 + def getPrevious: Node = previous // do NOT use in the result method + def getPrevious2: Node = previous2 + + def setNext(n: Node): Unit = next = n + def setNext2(n: Node): Unit = next2 = n + def setPrevious(n: Node): Unit = previous = n + def setPrevious2(n: Node): Unit = previous2 = n + + // Simplified Combiner interface + // Implements methods += and combine + // Abstract methods should be implemented in subclasses + abstract class DLLCombiner: + var first: Node = null // null for empty lists. + var last: Node = null // null for empty lists. + + var second: Node = null // null for empty lists. + var secondToLast: Node = null // null for empty lists. + + var size: Int = 0 + + // Adds an Integer to this array combiner. + def +=(elem: Int): Unit = + val node = new Node(elem) + if size == 0 then + first = node + last = node + size = 1 + else + last.setNext(node) + node.setPrevious(last) + node.setPrevious2(last.getPrevious) + if size > 1 then last.getPrevious.setNext2(node) + else second = node + secondToLast = last + last = node + size += 1 + + // Combines this array combiner and another given combiner in constant O(1) complexity. + def combine(that: DLLCombiner): DLLCombiner = + if this.size == 0 then that + else if that.size == 0 then this + else + this.last.setNext(that.first) + this.last.setNext2(that.first.getNext) + if this.last.getPrevious != null then + this.last.getPrevious.setNext2(that.first) // important + + that.first.setPrevious(this.last) + that.first.setPrevious2(this.last.getPrevious) + if that.first.getNext != null then that.first.getNext.setPrevious2(this.last) // important + + if this.size == 1 then second = that.first + + this.size = this.size + that.size + this.last = that.last + this.secondToLast = that.secondToLast + + this + + def task1(data: Array[Int]): ForkJoinTask[Unit] + def task2(data: Array[Int]): ForkJoinTask[Unit] + def task3(data: Array[Int]): ForkJoinTask[Unit] + def task4(data: Array[Int]): Unit + + def result(): Array[Int] + + def task[T](body: => T): ForkJoinTask[T] diff --git a/previous-exams/2022-final/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala b/previous-exams/2022-final/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala new file mode 100644 index 0000000..a650072 --- /dev/null +++ b/previous-exams/2022-final/concpar22final01/src/test/scala/concpar22final01/Problem1Suite.scala @@ -0,0 +1,491 @@ +package concpar22final01 + +import java.util.concurrent.* +import scala.util.DynamicVariable + +class Problem1Suite extends AbstractProblem1Suite: + + test("[Public] fetch simple result without combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + combiner1 += 1 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + + val result = combiner1.result() + val array = Array(7, 2, 3, 8, 1, 2, 3, 8) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result without combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + combiner1 += 3 + combiner1 += 8 + combiner1 += 1 + + val result = combiner1.result() + val array = Array(7, 2, 3, 8, 1) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result after simple combining (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 7 + combiner1 += 2 + + val combiner2 = new DLLCombinerTest + combiner2 += 3 + combiner2 += 8 + + val combiner3 = new DLLCombinerTest + combiner3 += 1 + combiner3 += 9 + + val combiner4 = new DLLCombinerTest + combiner4 += 3 + combiner4 += 2 + + val result = combiner1.combine(combiner2).combine(combiner3).combine(combiner4).result() + val array = Array(7, 2, 3, 8, 1, 9, 3, 2) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + test("[Public] fetch result - small combiner (2pts)") { + val combiner1 = new DLLCombinerTest + combiner1 += 4 + combiner1 += 2 + combiner1 += 6 + + val result = combiner1.result() + val array = Array(4, 2, 6) + + assert(Range(0, array.size).forall(i => array(i) == result(i))) + } + + + // (25+) 15 / 250 points for correct implementation, don't check parallelism + test("[Correctness] fetch result - simple combiners (2pts)") { + assertCorrectnessSimple() + } + + test("[Correctness] fetch result - small combiners (3pts)") { + assertCorrectnessBasic() + } + + test("[Correctness] fetch result - small combiners after combining (5pts)") { + assertCorrectnessCombined() + } + + test("[Correctness] fetch result - large combiners (5pts)") { + assertCorrectnessLarge() + } + + def assertCorrectnessSimple() = simpleCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessBasic() = basicCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessCombined() = + combinedCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + def assertCorrectnessLarge() = largeCombiners.foreach(elem => assert(compare(elem._1, elem._2))) + + // (25+15+) 25 / 250 points for correct parallel implementation, don't check if it's exactly 1/4 of the array per task + private var count = 0 + private val expected = 3 + + override def task[T](body: => T): ForkJoinTask[T] = + count += 1 + scheduler.value.schedule(body) + + test("[TaskCount] number of newly created tasks should be 3 (5pts)") { + assertTaskCountSimple() + } + + test("[TaskCount] fetch result and check parallel - simple combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessSimple() + } + + test("[TaskCount] fetch result and check parallel - small combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessBasic() + } + + test("[TaskCount] fetch result and check parallel - small combiners after combining (5pts)") { + assertTaskCountSimple() + assertCorrectnessCombined() + } + + test("[TaskCount] fetch result and check parallel - large combiners (5pts)") { + assertTaskCountSimple() + assertCorrectnessLarge() + } + + def assertTaskCountSimple(): Unit = + simpleCombiners.foreach(elem => assertTaskCount(elem._1, elem._2)) + + def assertTaskCount(combiner: DLLCombinerTest, array: Array[Int]): Unit = + try + count = 0 + build(combiner, array) + combiner.result() + assertEquals( + count, + expected, { + s"ERROR: Expected $expected instead of $count calls to `task(...)`" + } + ) + finally count = 0 + + // (25+15+25+) 50 / 250 points for correct implementation that uses only next2 and previous2, and not next and previous + test("[Skip2] fetch parallel result and check skip2 - simple combiners (10pts)") { + assertTaskCountSimple() + assertSkipSimple() + assertCorrectnessSimple() + } + + test("[Skip2] fetch result and check skip2 - simple combiners (10pts)") { + assertSkipSimple() + assertCorrectnessSimple() + } + + test("[Skip2] fetch result and check skip2 - small combiners (10pts)") { + assertSkipSimple() + assertCorrectnessBasic() + } + + test("[Skip2] fetch result and check skip2 - small combiners after combining (10pts)") { + assertSkipSimple() + assertCorrectnessCombined() + } + + test("[Skip2] fetch result and check skip2 - large combiners (10pts)") { + assertSkipSimple() + assertCorrectnessLarge() + } + + def assertSkipSimple(): Unit = simpleCombiners.foreach(elem => assertSkip(elem._1, elem._2)) + + def assertSkip(combiner: DLLCombinerTest, array: Array[Int]): Unit = + build(combiner, array) + combiner.result() + assertEquals( + combiner.nonSkipped, + false, { + s"ERROR: Calls to 'next' and 'previous' are not allowed! You should only use 'next2` and 'previous2' in your solution." + } + ) + + // (25+15+25+50+) 75 / 250 points for correct parallel implementation, exactly 1/4 of the array per task + test("[TaskFairness] each task should compute 1/4 of the result (15pts)") { + assertTaskFairness(simpleCombiners.unzip._1) + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - simple combiners (15pts)" + ) { + assertTaskFairness(simpleCombiners.unzip._1) + assertCorrectnessSimple() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - small combiners (15pts)" + ) { + assertTaskFairness(basicCombiners.unzip._1) + assertCorrectnessBasic() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - small combiners after combining (15pts)" + ) { + assertTaskFairness(combinedCombiners.unzip._1) + assertCorrectnessCombined() + } + + test( + "[TaskFairness] each task should correctly compute 1/4 of the result - large combiners (15pts)" + ) { + assertTaskFairness(largeCombiners.unzip._1) + assertCorrectnessLarge() + } + + def assertTaskFairness(combiners: List[DLLCombiner]): Unit = + def assertNewTaskFairness(combiner: DLLCombiner, task: ForkJoinTask[Unit], data: Array[Int]) = + var count = 0 + var expected = combiner.size / 4 + task.join + count = data.count(elem => elem != 0) + assert((count - expected).abs <= 1) + + def assertMainTaskFairness(combiner: DLLCombiner, task: Unit, data: Array[Int]) = + var count = 0 + var expected = combiner.size / 4 + count = data.count(elem => elem != 0) + assert((count - expected).abs <= 1) + + combiners.foreach { elem => + var data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task1(data), data) + + data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task2(data), data) + + data = Array.fill(elem.size)(0) + assertNewTaskFairness(elem, elem.task3(data), data) + + data = Array.fill(elem.size)(0) + assertMainTaskFairness(elem, elem.task4(data), data) + } + + // (25+15+25+50+75+) 60 / 250 points for correct parallel implementation, exactly 1/4 of the array per task, exactly the specified quarter + + test( + "[TaskPrecision] each task should compute specified 1/4 of the result - simple combiners (10pts)" + ) { + assertTaskPrecision(simpleCombiners) + } + + test( + "[TaskPrecision] task1 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision1(simpleCombiners) + } + + test( + "[TaskPrecision] task2 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision2(simpleCombiners) + } + + test( + "[TaskPrecision] task3 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision3(simpleCombiners) + } + + test( + "[TaskPrecision] task4 should compute specified 1/4 of the result - simple combiners (5pts)" + ) { + assertTaskPrecision4(simpleCombiners) + } + + test( + "[TaskPrecision] each task should compute specified 1/4 of the result - other combiners (30pts)" + ) { + assertTaskPrecision(basicCombiners) + assertTaskPrecision(combinedCombiners) + assertTaskPrecision(largeCombiners) + } + + def assertTaskPrecision(combiners: List[(DLLCombiner, Array[Int])]): Unit = + assertTaskPrecision1(combiners) + assertTaskPrecision2(combiners) + assertTaskPrecision3(combiners) + assertTaskPrecision4(combiners) + + def assertTaskPrecision1(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task1 = elem._1.task1(data) + task1.join + Range(0, elem._1.size).foreach(i => + (if i < elem._1.size / 2 - 1 && i % 2 == 0 then ref(i) = elem._2(i)) + ) + assert(Range(0, elem._1.size / 2 - 1).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision2(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task2 = elem._1.task2(data) + task2.join + Range(0, elem._1.size).foreach(i => + (if i < elem._1.size / 2 - 1 && i % 2 == 1 then ref(i) = elem._2(i)) + ) + assert(Range(0, elem._1.size / 2 - 1).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision3(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task3 = elem._1.task3(data) + task3.join + Range(0, elem._1.size).foreach(i => + (if i > elem._1.size / 2 + 1 && i % 2 == elem._1.size % 2 then ref(i) = elem._2(i)) + ) + assert(Range(elem._1.size / 2 + 2, elem._1.size).forall(i => data(i) == ref(i))) + } + + def assertTaskPrecision4(combiners: List[(DLLCombiner, Array[Int])]): Unit = + combiners.foreach { elem => + var data = Array.fill(elem._1.size)(0) + var ref = Array.fill(elem._1.size)(0) + val task4 = elem._1.task4(data) + Range(0, elem._1.size).foreach(i => + (if i > elem._1.size / 2 + 1 && i % 2 != elem._1.size % 2 then ref(i) = elem._2(i)) + ) + assert(Range(elem._1.size / 2 + 2, elem._1.size).forall(i => data(i) == ref(i))) + } + +trait AbstractProblem1Suite extends munit.FunSuite with LibImpl: + + def simpleCombiners = buildSimpleCombiners() + def basicCombiners = buildBasicCombiners() + def combinedCombiners = buildCombinedCombiners() + def largeCombiners = buildLargeCombiners() + + def buildSimpleCombiners() = + val simpleCombiners = List( + (new DLLCombinerTest, Array(4, 2, 6, 1, 5, 4, 3, 5, 6, 3, 4, 5, 6, 3, 4, 5)), + (new DLLCombinerTest, Array(7, 2, 2, 9, 3, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2)), + (new DLLCombinerTest, Array.fill(16)(5)) + ) + simpleCombiners.foreach(elem => build(elem._1, elem._2)) + simpleCombiners + + def buildBasicCombiners() = + val basicCombiners = List( + (new DLLCombinerTest, Array(4, 2, 6)), + (new DLLCombinerTest, Array(4, 1, 6)), + (new DLLCombinerTest, Array(7, 2, 2, 9, 3, 2, 11, 12, 5, 14, 15, 1, 17, 23)), + (new DLLCombinerTest, Array(7, 2, 9, 9, 3, 2, 11, 12, 13, 14, 15, 16, 17, 22)), + (new DLLCombinerTest, Array.fill(16)(7)), + (new DLLCombinerTest, Array.fill(16)(4)), + (new DLLCombinerTest, Array.fill(5)(3)), + (new DLLCombinerTest, Array.fill(5)(7)), + (new DLLCombinerTest, Array.fill(5)(4)) + ) + basicCombiners.foreach(elem => build(elem._1, elem._2)) + basicCombiners + + def buildCombinedCombiners() = + var combinedCombiners = List[(DLLCombiner, Array[Int])]() + + Range(1, 10).foreach { n => + val array = basicCombiners.filter(elem => elem._1.size == n).foldLeft(Array[Int]()) { + (acc, i) => acc ++ i._2 + } + val empty: DLLCombiner = new DLLCombinerTest + val combiner = basicCombiners.filter(elem => elem._1.size == n).map(_._1).foldLeft(empty) { + (acc, c) => acc.combine(c) + } + + combinedCombiners = combinedCombiners :+ (combiner, array) + } + combinedCombiners + + def buildLargeCombiners() = + val largeCombiners = List( + (new DLLCombinerTest, Array.fill(1321)(4) ++ Array.fill(1322)(7)), + (new DLLCombinerTest, Array.fill(1341)(2) ++ Array.fill(1122)(5)), + ( + new DLLCombinerTest, + Array.fill(1321)(4) ++ Array.fill(1322)(7) ++ Array.fill(321)(4) ++ Array.fill(322)(7) + ), + (new DLLCombinerTest, Array.fill(992321)(4) ++ Array.fill(99322)(7)), + (new DLLCombinerTest, Array.fill(953211)(4) ++ Array.fill(999322)(1)) + ) + largeCombiners.foreach(elem => build(elem._1, elem._2)) + largeCombiners + + def build(combiner: DLLCombinerTest, array: Array[Int]): DLLCombinerTest = + array.foreach(elem => combiner += elem) + combiner + + def compare(combiner: DLLCombiner, array: Array[Int]): Boolean = + val result = combiner.result() + Range(0, array.size).forall(i => array(i) == result(i)) + + def buildAndCompare(combiner: DLLCombinerTest, array: Array[Int]): Boolean = + array.foreach(elem => combiner += elem) + val result = combiner.result() + Range(0, array.size).forall(i => array(i) == result(i)) + +trait LibImpl extends Problem1: + + val forkJoinPool = new ForkJoinPool + + abstract class TaskScheduler: + def schedule[T](body: => T): ForkJoinTask[T] + + class DefaultTaskScheduler extends TaskScheduler: + def schedule[T](body: => T): ForkJoinTask[T] = + val t = new RecursiveTask[T]: + def compute = body + Thread.currentThread match + case wt: ForkJoinWorkerThread => + t.fork() + case _ => + forkJoinPool.execute(t) + t + + val scheduler = new DynamicVariable[TaskScheduler](new DefaultTaskScheduler) + + def task[T](body: => T): ForkJoinTask[T] = scheduler.value.schedule(body) + + class NodeTest(val v: Int, val myCombiner: DLLCombinerTest) extends Node(v): + override def getNext: Node = + myCombiner.nonSkipped = true + next + override def getNext2: Node = next2 + override def getPrevious: Node = + myCombiner.nonSkipped = true + previous + override def getPrevious2: Node = previous2 + override def setNext(n: Node): Unit = next = n + override def setNext2(n: Node): Unit = next2 = n + override def setPrevious(n: Node): Unit = previous = n + override def setPrevious2(n: Node): Unit = previous2 = n + + class DLLCombinerTest extends DLLCombinerImplementation: + var nonSkipped = false + override def result(): Array[Int] = + nonSkipped = false + super.result() + override def +=(elem: Int): Unit = + val node = new NodeTest(elem, this) + if size == 0 then + first = node + last = node + size = 1 + else + last.setNext(node) + node.setPrevious(last) + node.setPrevious2(last.getPrevious) + if size > 1 then last.getPrevious.setNext2(node) + else second = node + secondToLast = last + last = node + size += 1 + override def combine(that: DLLCombiner): DLLCombiner = + if this.size == 0 then that + else if that.size == 0 then this + else + this.last.setNext(that.first) + this.last.setNext2(that.first.getNext) + if this.last.getPrevious != null then + this.last.getPrevious.setNext2(that.first) // important + + that.first.setPrevious(this.last) + that.first.setPrevious2(this.last.getPrevious) + if that.first.getNext != null then that.first.getNext.setPrevious2(this.last) // important + + if this.size == 1 then second = that.first + + this.size = this.size + that.size + this.last = that.last + this.secondToLast = that.secondToLast + + this diff --git a/previous-exams/2022-final/concpar22final02.md b/previous-exams/2022-final/concpar22final02.md new file mode 100644 index 0000000..59dc15c --- /dev/null +++ b/previous-exams/2022-final/concpar22final02.md @@ -0,0 +1,38 @@ +# Problem 2: Wait and Notify + +## Setup + +Use the following commands to make a fresh clone of your repository: + +``` +git clone -b concpar22final02 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar22final02 +``` + +If you have issues with the IDE, try [reimporting the +build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), +if you still have problems, use `compile` in sbt instead. + +## Problem 2.1: Implement Barrier methods + +Your first task is to implement a _barrier_. A barrier acts as a synchronization point between multiple threads. It is used when you want all threads to finish a task before starting the next one. + +You have to complete the following two methods in the `Barrier` class: +1. `awaitZero` function waits for the count value to be zero. +2. `countDown` function decrements the count by one. It notifies other threads if count is less than or equal to zero. + +The barrier will be implemented using these functions. When the thread finish a task, it will decrement the count by using the `countDown` function. Then, it will call the `awaitZero` function to wait for other threads. + +## Problem 2.2: Use the Barrier to apply filters to an image + +In this part, each thread will apply an array of filters to each row of the image. Your task is to use the `Barrier` to act as a synchronization point while applying the filters. Each thread should wait for the other threads to complete the current filter before applying the next filter. + +`ImageLib.scala` provides an implementation of an image processing library with different filters. Each filter has a kernel which is applied on the image. `ImageLib.scala` implements four different filters. It provides an `applyFilter` method which applies a particular filter's kernel on a particular row on the input `Array` and generates the output in the output `Array`. + +The `ImageLib` class takes the size of an image as input. The image is a square matrix. The class has two buffers `buffer1` and `buffer2`. The initial image will be in `buffer1`. For the filtering task, you will switch between these buffers as input and output. For example, for the first filter `buffer1` will the input buffer and `buffer2` will be the output buffer. For the second filter `buffer2` will the input and `buffer1` will be the output and so on for subsequent filters. + +In `Problem2.scala` file where you will complete the `imagePipeline` function: + +- The `imagePipeline` function gets a array of filters and an array of row numbers. This filter needs to be applied to all the rows present in `row` array. After applying each filter, the thread has to wait for other threads to complete before applying the next filter. You will use barrier in this case. +- The `imagePipeline` function will return the output buffer. Note the output buffer can change between `buffer1` and `buffer2` depending on the number of filters applied. + +You can get partial points for solving parts of this exercise. diff --git a/previous-exams/2022-final/concpar22final02/.gitignore b/previous-exams/2022-final/concpar22final02/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final/concpar22final02/assignment.sbt b/previous-exams/2022-final/concpar22final02/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final/concpar22final02/build.sbt b/previous-exams/2022-final/concpar22final02/build.sbt new file mode 100644 index 0000000..61e5a6e --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final02" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "0.7.26" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final/concpar22final02/project/CourseraStudent.scala b/previous-exams/2022-final/concpar22final02/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final/concpar22final02/project/MOOCSettings.scala b/previous-exams/2022-final/concpar22final02/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final/concpar22final02/project/StudentTasks.scala b/previous-exams/2022-final/concpar22final02/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final/concpar22final02/project/build.properties b/previous-exams/2022-final/concpar22final02/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final/concpar22final02/project/buildSettings.sbt b/previous-exams/2022-final/concpar22final02/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final/concpar22final02/project/plugins.sbt b/previous-exams/2022-final/concpar22final02/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala new file mode 100644 index 0000000..decdbc9 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/AbstractBarrier.scala @@ -0,0 +1,11 @@ +package concpar22final02 + +import instrumentation.Monitor + +abstract class AbstractBarrier(val numThreads: Int) extends Monitor: + + var count = numThreads + + def awaitZero(): Unit + + def countDown(): Unit diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Barrier.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Barrier.scala new file mode 100644 index 0000000..cec8762 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Barrier.scala @@ -0,0 +1,7 @@ +package concpar22final02 + +class Barrier(numThreads: Int) extends AbstractBarrier(numThreads): + + def awaitZero():Unit = ??? + + def countDown():Unit = ??? diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala new file mode 100644 index 0000000..0f538ea --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/ImageLib.scala @@ -0,0 +1,47 @@ +package concpar22final02 + +import scala.collection.mutable.ArrayBuffer + +class ImageLib(size: Int): + + val buffer1: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(1) + val buffer2: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(0) + + enum Filter(val kernel: Array[Array[Int]]): + case Outline extends Filter(Array(Array(-1, -1, -1), Array(-1, 8, -1), Array(-1, -1, -1))) + case Sharpen extends Filter(Array(Array(0, -1, 0), Array(-1, 5, -1), Array(0, -1, 0))) + case Emboss extends Filter(Array(Array(-2, -1, 0), Array(-1, 1, 1), Array(0, 1, 2))) + case Identity extends Filter(Array(Array(0, 0, 0), Array(0, 1, 0), Array(0, 0, 0))) + + def init(input: ArrayBuffer[ArrayBuffer[Int]]) = + for i <- 0 to size - 1 do + for j <- 0 to size - 1 do + buffer1(i)(j) = input(i)(j) + + def computeConvolution( + kernel: Array[Array[Int]], + input: ArrayBuffer[ArrayBuffer[Int]], + row: Int, + column: Int + ): Int = + + val displacement = Array(-1, 0, 1) + var output = 0 + + for i <- 0 to 2 do + for j <- 0 to 2 do + val newI = row + displacement(i) + val newJ = column + displacement(j) + if newI < 0 || newI >= size || newJ < 0 || newJ >= size then output += 0 + else output += (kernel(i)(j) * input(newI)(newJ)) + + output + + def applyFilter( + kernel: Array[Array[Int]], + input: ArrayBuffer[ArrayBuffer[Int]], + output: ArrayBuffer[ArrayBuffer[Int]], + row: Int + ): Unit = + for i <- 0 to input(row).size - 1 do + output(row)(i) = computeConvolution(kernel, input, row, i) diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Problem2.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Problem2.scala new file mode 100644 index 0000000..b09951b --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/Problem2.scala @@ -0,0 +1,12 @@ +package concpar22final02 + +import java.util.concurrent.atomic.AtomicInteger +import scala.collection.mutable.ArrayBuffer + +class Problem2(imageSize: Int, numThreads: Int, numFilters: Int): + + val barrier: ArrayBuffer[Barrier] = ArrayBuffer.fill(numFilters)(Barrier(numThreads)) + + val imageLib: ImageLib = ImageLib(imageSize) + + def imagePipeline(filters: Array[imageLib.Filter], row: Array[Int]): ArrayBuffer[ArrayBuffer[Int]] = ??? diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala new file mode 100644 index 0000000..2718337 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Monitor.scala @@ -0,0 +1,32 @@ +package concpar22final02.instrumentation + +class Dummy + +trait Monitor: + implicit val dummy: Dummy = new Dummy + + def wait()(implicit i: Dummy) = waitDefault() + + def synchronized[T](e: => T)(implicit i: Dummy) = synchronizedDefault(e) + + def notify()(implicit i: Dummy) = notifyDefault() + + def notifyAll()(implicit i: Dummy) = notifyAllDefault() + + private val lock = new AnyRef + + // Can be overriden. + def waitDefault(): Unit = lock.wait() + def synchronizedDefault[T](toExecute: => T): T = lock.synchronized(toExecute) + def notifyDefault(): Unit = lock.notify() + def notifyAllDefault(): Unit = lock.notifyAll() + +trait LockFreeMonitor extends Monitor: + override def waitDefault() = + throw new Exception("Please use lock-free structures and do not use wait()") + override def synchronizedDefault[T](toExecute: => T): T = + throw new Exception("Please use lock-free structures and do not use synchronized()") + override def notifyDefault() = + throw new Exception("Please use lock-free structures and do not use notify()") + override def notifyAllDefault() = + throw new Exception("Please use lock-free structures and do not use notifyAll()") diff --git a/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala new file mode 100644 index 0000000..fb4a31e --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/main/scala/concpar22final02/instrumentation/Stats.scala @@ -0,0 +1,19 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ +package concpar22final02.instrumentation + +import java.lang.management.* + +/** A collection of methods that can be used to collect run-time statistics about Leon programs. + * This is mostly used to test the resources properties of Leon programs + */ +object Stats: + def timed[T](code: => T)(cont: Long => Unit): T = + var t1 = System.currentTimeMillis() + val r = code + cont((System.currentTimeMillis() - t1)) + r + + def withTime[T](code: => T): (T, Long) = + var t1 = System.currentTimeMillis() + val r = code + (r, (System.currentTimeMillis() - t1)) diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala new file mode 100644 index 0000000..95da2fc --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/Problem2Suite.scala @@ -0,0 +1,413 @@ +package concpar22final02 + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.mutable.HashMap +import scala.util.Random +import instrumentation.SchedulableProblem2 + +import instrumentation.TestHelper.* +import instrumentation.TestUtils.* +import scala.collection.mutable.ArrayBuffer + +class Problem2Suite extends munit.FunSuite: + + val imageSize = 5 + val nThreads = 3 + + def rowsForThread(threadNumber: Int): Array[Int] = + val start: Int = (imageSize * threadNumber) / nThreads + val end: Int = (imageSize * (threadNumber + 1)) / nThreads + (start until end).toArray + + test("Should work when barrier is called by a single thread (10pts)") { + testManySchedules( + 1, + sched => + val temp = new Problem2(imageSize, 1, 1) + ( + List(() => temp.barrier(0).countDown()), + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then + val notifyCount = sched.notifyCount + val notifyAllCount = sched.notifyAllCount + (false, s"No notify call $notifyCount $notifyAllCount") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when a single thread processes a single filter (10pts)") { + val temp = new Problem2(imageSize, 1, 1) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline(Array(temp.imageLib.Filter.Outline), Array(0, 1, 2, 3, 4)) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + } + + test("Should work when a single thread processes a 2 same filters (15pts)") { + val temp = new Problem2(imageSize, 1, 2) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array(temp.imageLib.Filter.Identity, temp.imageLib.Filter.Identity), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + } + + test("Should work when a single thread processes a 2 different filters (15pts)") { + val temp = new Problem2(imageSize, 1, 2) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array(temp.imageLib.Filter.Identity, temp.imageLib.Filter.Outline), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + ) + } + + test("Should work when barrier is called by two threads (25pts)") { + testManySchedules( + 2, + sched => + val temp = new Problem2(imageSize, 2, 1) + ( + List( + () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + , + () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + ), + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then (false, s"No notify call") + else if sched.waitCount == 0 then (false, s"No wait call") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when barrier is called by multiple threads (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new Problem2(imageSize, nThreads, 1) + ( + (for i <- 0 until nThreads yield () => + temp.barrier(0).countDown() + temp.barrier(0).awaitZero() + ).toList, + results => + if sched.notifyCount == 0 && sched.notifyAllCount == 0 then (false, s"No notify call") + else if sched.waitCount == 0 then (false, s"No wait call") + else if temp.barrier(0).count != 0 then + val count = temp.barrier(0).count + (false, s"Barrier count not equal to zero: $count") + else (true, "") + ) + ) + } + + test("Should work when a single thread processes a multiple same filters (25pts)") { + val temp = new Problem2(imageSize, 1, 3) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array( + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Outline + ), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-128, -173, -107, -173, -128), + ArrayBuffer(205, -2, 172, -2, 205), + ArrayBuffer(322, -128, 208, -128, 322), + ArrayBuffer(55, -854, -428, -854, 55), + ArrayBuffer(1180, 433, 751, 433, 1180) + ) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-16, -22, -18, -22, -16), + ArrayBuffer(23, -1, 9, -1, 23), + ArrayBuffer(36, -18, 0, -18, 36), + ArrayBuffer(29, -67, -45, -67, 29), + ArrayBuffer(152, 74, 90, 74, 152) + ) + ) + } + + test("Should work when a single thread processes multiple filters (25pts)") { + val temp = new Problem2(imageSize, 1, 3) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + temp.imagePipeline( + Array( + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Sharpen + ), + Array(0, 1, 2, 3, 4) + ) + assertEquals( + temp.imageLib.buffer1, + ArrayBuffer( + ArrayBuffer(-2, -3, -3, -3, -2), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(6, 0, 0, 0, 6), + ArrayBuffer(9, 0, 0, 0, 9), + ArrayBuffer(22, 15, 15, 15, 22) + ) + ) + assertEquals( + temp.imageLib.buffer2, + ArrayBuffer( + ArrayBuffer(-10, -10, -9, -10, -10), + ArrayBuffer(11, 0, 3, 0, 11), + ArrayBuffer(18, -6, 0, -6, 18), + ArrayBuffer(17, -24, -15, -24, 17), + ArrayBuffer(86, 38, 45, 38, 86) + ) + ) + } + + test("Should work when multiple thread processes a single filter (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 1) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline(Array(temp.imageLib.Filter.Outline), rowsForThread(i))).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(1, 1, 1, 1, 1) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(5, 3, 3, 3, 5), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(5, 3, 3, 3, 5) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes two filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 2) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array(temp.imageLib.Filter.Outline, temp.imageLib.Filter.Sharpen), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(19, 7, 9, 7, 19), + ArrayBuffer(7, -6, -3, -6, 7), + ArrayBuffer(9, -3, 0, -3, 9), + ArrayBuffer(7, -6, -3, -6, 7), + ArrayBuffer(19, 7, 9, 7, 19) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(5, 3, 3, 3, 5), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(3, 0, 0, 0, 3), + ArrayBuffer(5, 3, 3, 3, 5) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes multiple same filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 4) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array( + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Identity + ), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(0, 0, 0, 0, 0), + ArrayBuffer(1, 1, 1, 1, 1), + ArrayBuffer(2, 2, 2, 2, 2), + ArrayBuffer(3, 3, 3, 3, 3), + ArrayBuffer(4, 4, 4, 4, 4) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } + + test("Should work when multiple thread processes multiple different filters (25pts)") { + testManySchedules( + nThreads, + sched => + val temp = new SchedulableProblem2(sched, imageSize, nThreads, 4) + val buf: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer() + for i: Int <- 0 until imageSize do buf += ArrayBuffer.fill(5)(i) + temp.imageLib.init(buf) + ( + (for i <- 0 until nThreads + yield () => + temp.imagePipeline( + Array( + temp.imageLib.Filter.Outline, + temp.imageLib.Filter.Sharpen, + temp.imageLib.Filter.Identity, + temp.imageLib.Filter.Sharpen + ), + rowsForThread(i) + )).toList, + results => + val expected_buffer1 = ArrayBuffer( + ArrayBuffer(-51, -31, -28, -31, -51), + ArrayBuffer(47, 2, 24, 2, 47), + ArrayBuffer(68, -24, 24, -24, 68), + ArrayBuffer(5, -154, -72, -154, 5), + ArrayBuffer(375, 83, 164, 83, 375) + ) + val expected_buffer2 = ArrayBuffer( + ArrayBuffer(-10, -10, -9, -10, -10), + ArrayBuffer(11, 0, 3, 0, 11), + ArrayBuffer(18, -6, 0, -6, 18), + ArrayBuffer(17, -24, -15, -24, 17), + ArrayBuffer(86, 38, 45, 38, 86) + ) + val res_buffer1 = temp.imageLib.buffer1 + val res_buffer2 = temp.imageLib.buffer2 + if res_buffer1 != expected_buffer1 then + (false, s"Buffer1 expected: $expected_buffer1 , got $res_buffer1") + else if res_buffer2 != expected_buffer2 then + (false, s"Buffer2 expected: $expected_buffer2 , got $res_buffer2") + else (true, "") + ) + ) + } diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala new file mode 100644 index 0000000..645f9cb --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/MockedMonitor.scala @@ -0,0 +1,57 @@ +package concpar22final02.instrumentation + +trait MockedMonitor extends Monitor: + def scheduler: Scheduler + + // Can be overriden. + override def waitDefault() = + scheduler.log("wait") + scheduler.waitCount.incrementAndGet() + scheduler updateThreadState Wait(this, scheduler.threadLocks.tail) + override def synchronizedDefault[T](toExecute: => T): T = + scheduler.log("synchronized check") + val prevLocks = scheduler.threadLocks + scheduler updateThreadState Sync( + this, + prevLocks + ) // If this belongs to prevLocks, should just continue. + scheduler.log("synchronized -> enter") + try toExecute + finally + scheduler updateThreadState Running(prevLocks) + scheduler.log("synchronized -> out") + override def notifyDefault() = + scheduler mapOtherStates { state => + state match + case Wait(lockToAquire, locks) if lockToAquire == this => SyncUnique(this, state.locks) + case e => e + } + scheduler.notifyCount.incrementAndGet() + scheduler.log("notify") + override def notifyAllDefault() = + scheduler mapOtherStates { state => + state match + case Wait(lockToAquire, locks) if lockToAquire == this => Sync(this, state.locks) + case SyncUnique(lockToAquire, locks) if lockToAquire == this => Sync(this, state.locks) + case e => e + } + scheduler.notifyAllCount.incrementAndGet() + scheduler.log("notifyAll") + +abstract class ThreadState: + def locks: Seq[AnyRef] +trait CanContinueIfAcquiresLock extends ThreadState: + def lockToAquire: AnyRef +case object Start extends ThreadState: + def locks: Seq[AnyRef] = Seq.empty +case object End extends ThreadState: + def locks: Seq[AnyRef] = Seq.empty +case class Wait(lockToAquire: AnyRef, locks: Seq[AnyRef]) extends ThreadState +case class SyncUnique(lockToAquire: AnyRef, locks: Seq[AnyRef]) + extends ThreadState + with CanContinueIfAcquiresLock +case class Sync(lockToAquire: AnyRef, locks: Seq[AnyRef]) + extends ThreadState + with CanContinueIfAcquiresLock +case class Running(locks: Seq[AnyRef]) extends ThreadState +case class VariableReadWrite(locks: Seq[AnyRef]) extends ThreadState diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala new file mode 100644 index 0000000..a14587b --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/SchedulableBarrier.scala @@ -0,0 +1,20 @@ +package concpar22final02.instrumentation + +import scala.annotation.tailrec +import concpar22final02.* +import scala.collection.mutable.ArrayBuffer + +class SchedulableBarrier(val scheduler: Scheduler, size: Int) + extends Barrier(size) + with MockedMonitor + +class SchedulableProblem2( + val scheduler: Scheduler, + imageSize: Int, + threadCount: Int, + numFilters: Int +) extends Problem2(imageSize, threadCount, numFilters): + self => + + override val barrier = + ArrayBuffer.fill(numFilters)(SchedulableBarrier(scheduler, threadCount)) diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala new file mode 100644 index 0000000..4001ee3 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/Scheduler.scala @@ -0,0 +1,294 @@ +package concpar22final02.instrumentation + +import java.util.concurrent.* +import scala.concurrent.duration.* +import scala.collection.mutable.* +import Stats.* + +import java.util.concurrent.atomic.AtomicInteger + +sealed abstract class Result +case class RetVal(rets: List[Any]) extends Result +case class Except(msg: String, stackTrace: Array[StackTraceElement]) extends Result +case class Timeout(msg: String) extends Result + +/** A class that maintains schedule and a set of thread ids. The schedules are advanced after an + * operation of a SchedulableBuffer is performed. Note: the real schedule that is executed may + * deviate from the input schedule due to the adjustments that had to be made for locks + */ +class Scheduler(sched: List[Int]): + val maxOps = 500 // a limit on the maximum number of operations the code is allowed to perform + + var waitCount:AtomicInteger = new AtomicInteger(0) + var notifyCount:AtomicInteger = new AtomicInteger(0) + var notifyAllCount:AtomicInteger = new AtomicInteger(0) + + private var schedule = sched + var numThreads = 0 + private val realToFakeThreadId = Map[Long, Int]() + private val opLog = ListBuffer[String]() // a mutable list (used for efficient concat) + private val threadStates = Map[Int, ThreadState]() + + /** Runs a set of operations in parallel as per the schedule. Each operation may consist of many + * primitive operations like reads or writes to shared data structure each of which should be + * executed using the function `exec`. + * @timeout + * in milliseconds + * @return + * true - all threads completed on time, false -some tests timed out. + */ + def runInParallel(timeout: Long, ops: List[() => Any]): Result = + numThreads = ops.length + val threadRes = Array.fill(numThreads) { None: Any } + var exception: Option[(Throwable, Int)] = None + val syncObject = new Object() + var completed = new AtomicInteger(0) + // create threads + val threads = ops.zipWithIndex.map { case (op, i) => + new Thread( + new Runnable(): + def run(): Unit = + val fakeId = i + 1 + setThreadId(fakeId) + try + updateThreadState(Start) + val res = op() + updateThreadState(End) + threadRes(i) = res + // notify the main thread if all threads have completed + if completed.incrementAndGet() == ops.length then + syncObject.synchronized { syncObject.notifyAll() } + catch + case e: Throwable if exception != None => // do nothing here and silently fail + case e: Throwable => + log(s"throw ${e.toString}") + exception = Some((e, fakeId)) + syncObject.synchronized { syncObject.notifyAll() } + // println(s"$fakeId: ${e.toString}") + // Runtime.getRuntime().halt(0) //exit the JVM and all running threads (no other way to kill other threads) + ) + } + // start all threads + threads.foreach(_.start()) + // wait for all threads to complete, or for an exception to be thrown, or for the time out to expire + var remTime = timeout + syncObject.synchronized { + timed { + if completed.get() != ops.length then syncObject.wait(timeout) } { time => + remTime -= time + } + } + if exception.isDefined then + Except( + s"Thread ${exception.get._2} crashed on the following schedule: \n" + opLog.mkString("\n"), + exception.get._1.getStackTrace + ) + else if remTime <= 1 then // timeout ? using 1 instead of zero to allow for some errors + Timeout(opLog.mkString("\n")) + else + // every thing executed normally + RetVal(threadRes.toList) + + // Updates the state of the current thread + def updateThreadState(state: ThreadState): Unit = + val tid = threadId + synchronized { + threadStates(tid) = state + } + state match + case Sync(lockToAquire, locks) => + if locks.indexOf(lockToAquire) < 0 then waitForTurn + else + // Re-aqcuiring the same lock + updateThreadState(Running(lockToAquire +: locks)) + case Start => waitStart() + case End => removeFromSchedule(tid) + case Running(_) => + case _ => waitForTurn // Wait, SyncUnique, VariableReadWrite + + def waitStart(): Unit = + // while (threadStates.size < numThreads) { + // Thread.sleep(1) + // } + synchronized { + if threadStates.size < numThreads then wait() + else notifyAll() + } + + def threadLocks = + synchronized { + threadStates(threadId).locks + } + + def threadState = + synchronized { + threadStates(threadId) + } + + def mapOtherStates(f: ThreadState => ThreadState) = + val exception = threadId + synchronized { + for k <- threadStates.keys if k != exception do threadStates(k) = f(threadStates(k)) + } + + def log(str: String) = + if (realToFakeThreadId contains Thread.currentThread().getId()) then + val space = (" " * ((threadId - 1) * 2)) + val s = space + threadId + ":" + "\n".r.replaceAllIn(str, "\n" + space + " ") + opLog += s + + /** Executes a read or write operation to a global data structure as per the given schedule + * @param msg + * a message corresponding to the operation that will be logged + */ + def exec[T](primop: => T)(msg: => String, postMsg: => Option[T => String] = None): T = + if !(realToFakeThreadId contains Thread.currentThread().getId()) then primop + else + updateThreadState(VariableReadWrite(threadLocks)) + val m = msg + if m != "" then log(m) + if opLog.size > maxOps then + throw new Exception( + s"Total number of reads/writes performed by threads exceed $maxOps. A possible deadlock!" + ) + val res = primop + postMsg match + case Some(m) => log(m(res)) + case None => + res + + private def setThreadId(fakeId: Int) = synchronized { + realToFakeThreadId(Thread.currentThread.getId) = fakeId + } + + def threadId = + try realToFakeThreadId(Thread.currentThread().getId()) + catch + case e: NoSuchElementException => + throw new Exception( + "You are accessing shared variables in the constructor. This is not allowed. The variables are already initialized!" + ) + + private def isTurn(tid: Int) = synchronized { + (!schedule.isEmpty && schedule.head != tid) + } + + def canProceed(): Boolean = + val tid = threadId + canContinue match + case Some((i, state)) if i == tid => + // println(s"$tid: Runs ! Was in state $state") + canContinue = None + state match + case Sync(lockToAquire, locks) => updateThreadState(Running(lockToAquire +: locks)) + case SyncUnique(lockToAquire, locks) => + mapOtherStates { + _ match + case SyncUnique(lockToAquire2, locks2) if lockToAquire2 == lockToAquire => + Wait(lockToAquire2, locks2) + case e => e + } + updateThreadState(Running(lockToAquire +: locks)) + case VariableReadWrite(locks) => updateThreadState(Running(locks)) + true + case Some((i, state)) => + // println(s"$tid: not my turn but $i !") + false + case None => + false + + var threadPreference = + 0 // In the case the schedule is over, which thread should have the preference to execute. + + /** returns true if the thread can continue to execute, and false otherwise */ + def decide(): Option[(Int, ThreadState)] = + if !threadStates.isEmpty + then // The last thread who enters the decision loop takes the decision. + // println(s"$threadId: I'm taking a decision") + if threadStates.values.forall { + case e: Wait => true + case _ => false + } + then + val waiting = threadStates.keys.map(_.toString).mkString(", ") + val s = if threadStates.size > 1 then "s" else "" + val are = if threadStates.size > 1 then "are" else "is" + throw new Exception( + s"Deadlock: Thread$s $waiting $are waiting but all others have ended and cannot notify them." + ) + else + // Threads can be in Wait, Sync, SyncUnique, and VariableReadWrite mode. + // Let's determine which ones can continue. + val notFree = threadStates.collect { case (id, state) => state.locks }.flatten.toSet + val threadsNotBlocked = threadStates.toSeq.filter { + case (id, v: VariableReadWrite) => true + case (id, v: CanContinueIfAcquiresLock) => + !notFree(v.lockToAquire) || (v.locks contains v.lockToAquire) + case _ => false + } + if threadsNotBlocked.isEmpty then + val waiting = threadStates.keys.map(_.toString).mkString(", ") + val s = if threadStates.size > 1 then "s" else "" + val are = if threadStates.size > 1 then "are" else "is" + val whoHasLock = threadStates.toSeq.flatMap { case (id, state) => + state.locks.map(lock => (lock, id)) + }.toMap + val reason = threadStates + .collect { + case (id, state: CanContinueIfAcquiresLock) if !notFree(state.lockToAquire) => + s"Thread $id is waiting on lock ${state.lockToAquire} held by thread ${whoHasLock(state.lockToAquire)}" + } + .mkString("\n") + throw new Exception(s"Deadlock: Thread$s $waiting are interlocked. Indeed:\n$reason") + else if threadsNotBlocked.size == 1 + then // Do not consume the schedule if only one thread can execute. + Some(threadsNotBlocked(0)) + else + val next = + schedule.indexWhere(t => threadsNotBlocked.exists { case (id, state) => id == t }) + if next != -1 then + // println(s"$threadId: schedule is $schedule, next chosen is ${schedule(next)}") + val chosenOne = schedule(next) // TODO: Make schedule a mutable list. + schedule = schedule.take(next) ++ schedule.drop(next + 1) + Some((chosenOne, threadStates(chosenOne))) + else + threadPreference = (threadPreference + 1) % threadsNotBlocked.size + val chosenOne = threadsNotBlocked(threadPreference) // Maybe another strategy + Some(chosenOne) + // threadsNotBlocked.indexOf(threadId) >= 0 + /* + val tnb = threadsNotBlocked.map(_._1).mkString(",") + val s = if (schedule.isEmpty) "empty" else schedule.mkString(",") + val only = if (schedule.isEmpty) "" else " only" + throw new Exception(s"The schedule is $s but$only threads ${tnb} can continue")*/ + else canContinue + + /** This will be called before a schedulable operation begins. This should not use synchronized + */ + var numThreadsWaiting = new AtomicInteger(0) + // var waitingForDecision = Map[Int, Option[Int]]() // Mapping from thread ids to a number indicating who is going to make the choice. + var canContinue: Option[(Int, ThreadState)] = + None // The result of the decision thread Id of the thread authorized to continue. + private def waitForTurn = + synchronized { + if numThreadsWaiting.incrementAndGet() == threadStates.size then + canContinue = decide() + notifyAll() + // waitingForDecision(threadId) = Some(numThreadsWaiting) + // println(s"$threadId Entering waiting with ticket number $numThreadsWaiting/${waitingForDecision.size}") + while !canProceed() do wait() + } + numThreadsWaiting.decrementAndGet() + + /** To be invoked when a thread is about to complete + */ + private def removeFromSchedule(fakeid: Int) = synchronized { + // println(s"$fakeid: I'm taking a decision because I finished") + schedule = schedule.filterNot(_ == fakeid) + threadStates -= fakeid + if numThreadsWaiting.get() == threadStates.size then + canContinue = decide() + notifyAll() + } + + def getOperationLog() = opLog diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala new file mode 100644 index 0000000..c4bcda0 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestHelper.scala @@ -0,0 +1,127 @@ +package concpar22final02.instrumentation + +import scala.util.Random +import scala.collection.mutable.{Map as MutableMap} + +import Stats.* + +object TestHelper: + val noOfSchedules = 10000 // set this to 100k during deployment + val readWritesPerThread = 20 // maximum number of read/writes possible in one thread + val contextSwitchBound = 10 + val testTimeout = 240 // the total time out for a test in seconds + val schedTimeout = 15 // the total time out for execution of a schedule in secs + + // Helpers + /*def testManySchedules(op1: => Any): Unit = testManySchedules(List(() => op1)) + def testManySchedules(op1: => Any, op2: => Any): Unit = testManySchedules(List(() => op1, () => op2)) + def testManySchedules(op1: => Any, op2: => Any, op3: => Any): Unit = testManySchedules(List(() => op1, () => op2, () => op3)) + def testManySchedules(op1: => Any, op2: => Any, op3: => Any, op4: => Any): Unit = testManySchedules(List(() => op1, () => op2, () => op3, () => op4))*/ + + def testSequential[T](ops: Scheduler => Any)(assertions: T => (Boolean, String)) = + testManySchedules( + 1, + (sched: Scheduler) => + (List(() => ops(sched)), (res: List[Any]) => assertions(res.head.asInstanceOf[T])) + ) + + /** @numThreads + * number of threads + * @ops + * operations to be executed, one per thread + * @assertion + * as condition that will executed after all threads have completed (without exceptions) the + * arguments are the results of the threads + */ + def testManySchedules( + numThreads: Int, + ops: Scheduler => ( + List[() => Any], // Threads + List[Any] => (Boolean, String) + ) // Assertion + ) = + var timeout = testTimeout * 1000L + val threadIds = (1 to numThreads) + // (1 to scheduleLength).flatMap(_ => threadIds).toList.permutations.take(noOfSchedules).foreach { + val schedules = (new ScheduleGenerator(numThreads)).schedules() + var schedsExplored = 0 + schedules.takeWhile(_ => schedsExplored <= noOfSchedules && timeout > 0).foreach { + // case _ if timeout <= 0 => // break + case schedule => + schedsExplored += 1 + val schedr = new Scheduler(schedule) + // println("Exploring Sched: "+schedule) + val (threadOps, assertion) = ops(schedr) + if threadOps.size != numThreads then + throw new IllegalStateException( + s"Number of threads: $numThreads, do not match operations of threads: $threadOps" + ) + timed { schedr.runInParallel(schedTimeout * 1000, threadOps) } { t => timeout -= t } match + case Timeout(msg) => + throw new java.lang.AssertionError( + "assertion failed\n" + "The schedule took too long to complete. A possible deadlock! \n" + msg + ) + case Except(msg, stkTrace) => + val traceStr = + "Thread Stack trace: \n" + stkTrace.map(" at " + _.toString).mkString("\n") + throw new java.lang.AssertionError("assertion failed\n" + msg + "\n" + traceStr) + case RetVal(threadRes) => + // check the assertion + val (success, custom_msg) = assertion(threadRes) + if !success then + val msg = + "The following schedule resulted in wrong results: \n" + custom_msg + "\n" + schedr + .getOperationLog() + .mkString("\n") + throw new java.lang.AssertionError("Assertion failed: " + msg) + } + if timeout <= 0 then + throw new java.lang.AssertionError( + "Test took too long to complete! Cannot check all schedules as your code is too slow!" + ) + + /** A schedule generator that is based on the context bound + */ + class ScheduleGenerator(numThreads: Int): + val scheduleLength = readWritesPerThread * numThreads + val rands = (1 to scheduleLength).map(i => new Random(0xcafe * i)) // random numbers for choosing a thread at each position + def schedules(): LazyList[List[Int]] = + var contextSwitches = 0 + var contexts = List[Int]() // a stack of thread ids in the order of context-switches + val remainingOps = MutableMap[Int, Int]() + remainingOps ++= (1 to numThreads).map(i => + (i, readWritesPerThread) + ) // num ops remaining in each thread + val liveThreads = (1 to numThreads).toSeq.toBuffer + + /** Updates remainingOps and liveThreads once a thread is chosen for a position in the + * schedule + */ + def updateState(tid: Int): Unit = + val remOps = remainingOps(tid) + if remOps == 0 then liveThreads -= tid + else remainingOps += (tid -> (remOps - 1)) + val schedule = rands.foldLeft(List[Int]()) { + case (acc, r) if contextSwitches < contextSwitchBound => + val tid = liveThreads(r.nextInt(liveThreads.size)) + contexts match + case prev :: tail if prev != tid => // we have a new context switch here + contexts +:= tid + contextSwitches += 1 + case prev :: tail => + case _ => // init case + contexts +:= tid + updateState(tid) + acc :+ tid + case ( + acc, + _ + ) => // here context-bound has been reached so complete the schedule without any more context switches + if !contexts.isEmpty then contexts = contexts.dropWhile(remainingOps(_) == 0) + val tid = contexts match + case top :: tail => top + case _ => liveThreads(0) // here, there has to be threads that have not even started + updateState(tid) + acc :+ tid + } + schedule #:: schedules() diff --git a/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala new file mode 100644 index 0000000..5c76ec9 --- /dev/null +++ b/previous-exams/2022-final/concpar22final02/src/test/scala/concpar22final02/instrumentation/TestUtils.scala @@ -0,0 +1,14 @@ +package concpar22final02.instrumentation + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.concurrent.ExecutionContext.Implicits.global + +object TestUtils: + def failsOrTimesOut[T](action: => T): Boolean = + val asyncAction = Future { + action + } + try Await.result(asyncAction, 2000.millisecond) + catch case _: Throwable => return true + return false diff --git a/previous-exams/2022-final/concpar22final03.md b/previous-exams/2022-final/concpar22final03.md new file mode 100644 index 0000000..84cea5e --- /dev/null +++ b/previous-exams/2022-final/concpar22final03.md @@ -0,0 +1,37 @@ +# Problem 3: Futures + +## Setup + +Use the following commands to make a fresh clone of your repository: + +``` +git clone -b concpar22final03 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar22final03 +``` + +If you have issues with the IDE, try [reimporting the +build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), +if you still have problems, use `compile` in sbt instead. + +## Exercise + +The famous trading cards game Scala: The Programming has recently been gaining traction. +In this little exercise, you will propose to clients your services as a trader: Buying and selling cards in groups on demand. + +You are provided in the file `Economics.scala` with an interface to handle asynchronous buying and selling of cards and management of your money. Do not modify this file. More precisely, this interface defines: + +- A `Card`, which has a `name` and which you can own (`isMine == true`) or not (`isMine == false`). This is only to prevent a card from being sold or used multiple times, and you may not need it. You can find the value of a card using `valueOf`. +- A `MoneyBag`, which is a container to transport money. Similarly, the money inside a bag can only be used once. The function `moneyIn` informs you of the bag's value, should you need it. +- The function `sellCard`, which will sell a card through a `Future` and gives you back a `Future[MoneyBag]`. If you do not own the card, the `Future` will fail. +- The function `buyCard`, which will consume a given `MoneyBag` and handle you, through a `Future`, the requested card. The provided bag must contain the exact amount of money corresponding to the card's value. +- Finally, you have a bank account with the following functions: + - `balance`: indicates your current monetary possession + - `withdraw`: substracts a given amount from your balance, and handles you a corresponding `Future[MoneyBag]` + - `deposit`: consumes a moneyBag and returns a `Future[Unit]` when the balance is updated. Note that you should not deposit empty moneyBags! If you do, you will get a failure, possibly indicating that you try to deposit the same bag twice. + +Your task in the exercise is to implement the function `orderDeck` in the file `Problem3.scala`. In a `Future`, start by checking that the sum of the money and the value of the cards the client gives you is large enough to buy the requested list of cards. If it is not, then the future should fail with a `NotEnoughMoneyException`. + +Then, sell all provided cards and put the received moneyBags in your bank accounts by chaining asynchronously the `Futures` of `sellCard` and `deposit`. You will obtain a `List[Future[Unit]]`, which should be converted into a `Future[Unit]` (so that when this `Future` returns, all deposits have finished). Those steps are provided for you in the helper function `sellListOfCards`. + +Then, do the opposite: withdraw `MoneyBags` of adequate value and use them to buy cards. Finally agregate the `List[Future[Card]]` into a `Future[List[Card]]`. You can implement those steps into the `buyListOfCards` function. Take inspiration from the given example `sellListOfCards`, and combine them in the `orderDeck` function. + +Final tip: Make good use of `map`, `flatMap` and `zip` on futures. diff --git a/previous-exams/2022-final/concpar22final03/.gitignore b/previous-exams/2022-final/concpar22final03/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final/concpar22final03/assignment.sbt b/previous-exams/2022-final/concpar22final03/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final/concpar22final03/build.sbt b/previous-exams/2022-final/concpar22final03/build.sbt new file mode 100644 index 0000000..19eefd4 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/build.sbt @@ -0,0 +1,11 @@ +course := "concpar" +assignment := "concpar22final03" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final/concpar22final03/project/CourseraStudent.scala b/previous-exams/2022-final/concpar22final03/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final/concpar22final03/project/MOOCSettings.scala b/previous-exams/2022-final/concpar22final03/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final/concpar22final03/project/StudentTasks.scala b/previous-exams/2022-final/concpar22final03/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final/concpar22final03/project/build.properties b/previous-exams/2022-final/concpar22final03/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final/concpar22final03/project/buildSettings.sbt b/previous-exams/2022-final/concpar22final03/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final/concpar22final03/project/plugins.sbt b/previous-exams/2022-final/concpar22final03/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Economics.scala b/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Economics.scala new file mode 100644 index 0000000..c032714 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Economics.scala @@ -0,0 +1,44 @@ +package concpar22final03 + +import scala.concurrent.Future + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.util.Random + +trait Economics: + + /** A trading card from the game Scala: The Programming. We can own a card, but once don't + * anymore. + */ + final class Card(val name: String) + def isMine(c: Card): Boolean + + /** This function uses the best available database to return the sell value of a card on the + * market. + */ + def valueOf(cardName: String): Int = List(1, cardName.length).max + + /** This method represents an exact amount of money that can be hold, spent, or put in the bank + */ + final class MoneyBag() + def moneyIn(m: MoneyBag): Int + + /** If you sell a card, at some point in the future you will get some money (in a bag). + */ + def sellCard(c: Card): Future[MoneyBag] + + /** You can buy any "Scala: The Programming" card by providing a bag of money with the appropriate + * amount and waiting for the transaction to take place. You will own the returned card. + */ + def buyCard(money: MoneyBag, name: String): Future[Card] + + /** This simple bank account holds money for you. You can bring a money bag to increase your + * account's balance, or withdraw a money bag of any size not greater than your account's + * balance. + */ + def balance: Int + def withdraw(amount: Int): Future[MoneyBag] + def deposit(bag: MoneyBag): Future[Unit] + + class NotEnoughMoneyException extends Exception("Not enough money provided to buy those cards") \ No newline at end of file diff --git a/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Problem3.scala b/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Problem3.scala new file mode 100644 index 0000000..46c5a92 --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/src/main/scala/concpar22final03/Problem3.scala @@ -0,0 +1,38 @@ +package concpar22final03 + +import scala.concurrent.Future +import concurrent.ExecutionContext.Implicits.global + +trait Problem3: + val economics: Economics + import economics.* + + /** The objective is to propose a service of deck building. People come to you with some money and + * some cards they want to sell, and you need to return them a complete deck of the cards they + * want. + */ + def orderDeck( + bag: MoneyBag, + cardsToSell: List[Card], + wantedDeck: List[String] + ): Future[List[Card]] = + Future { + ??? // : Future[List[Card]] + }.flatten + + /** This helper function will sell the provided list of cards and put the money on your personal + * bank account. It returns a Future of Unit, which indicates when all sales are completed. + */ + def sellListOfCards(cardsToSell: List[Card]): Future[Unit] = + val moneyFromSales: List[Future[Unit]] = cardsToSell.map { c => + sellCard(c).flatMap(m => deposit(m).map { _ => }) + } + Future + .sequence(moneyFromSales) + .map(_ => ()) // Future.sequence transforms a List[Future[A]] into a Future[List[A]] + + /** This helper function, given a list of wanted card names and assuming there is enough money in + * the bank account, will buy (in the future) those cards, and return them. + */ + def buyListOfCards(wantedDeck: List[String]): Future[List[Card]] = + ??? diff --git a/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala b/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala new file mode 100644 index 0000000..a8c327e --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/EconomicsTest.scala @@ -0,0 +1,98 @@ +package concpar22final03 + +import scala.concurrent.Future + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.util.Random + +trait EconomicsTest extends Economics: + val ownedCards: collection.mutable.Set[Card] = collection.mutable.Set[Card]() + def owned(c: Card): Boolean = ownedCards(c) + def isMine(c: Card): Boolean = ownedCards(c) + + override def valueOf(cardName: String): Int = List(1, cardName.length).max + + /** This is a container for an exact amount of money that can be hold, spent, or put in the bank + */ + val moneyInMoneyBag = collection.mutable.Map[MoneyBag, Int]() + def moneyIn(m: MoneyBag): Int = moneyInMoneyBag.getOrElse(m, 0) + + /** If you sell a card, at some point in the future you will get some money (in a bag). + */ + def sellCard(c: Card): Future[MoneyBag] = + Future { + Thread.sleep(sellWaitTime()) + synchronized( + if owned(c) then + ownedCards.remove(c) + getMoneyBag(valueOf(c.name)) + else + throw Exception( + "This card doesn't belong to you or has already been sold, you can't sell it." + ) + ) + } + + /** You can buy any "Scala: The Programming" card by providing a bag of money with the appropriate + * amount and waiting for the transaction to take place + */ + def buyCard(bag: MoneyBag, name: String): Future[Card] = + Future { + Thread.sleep(buyWaitTime()) + synchronized { + if moneyIn(bag) != valueOf(name) then + throw Exception( + "You didn't provide the exact amount of money necessary to buy this card." + ) + else moneyInMoneyBag.update(bag, 0) + getCard(name) + } + + } + + /** This simple bank account hold money for you. You can bring a money bag to increase your + * account, or withdraw a money bag of any size not greater than your account's balance. + */ + private var balance_ = initialBalance() + def balance: Int = balance_ + def withdraw(amount: Int): Future[MoneyBag] = + Future { + Thread.sleep(withdrawWaitTime()) + synchronized( + if balance_ >= amount then + balance_ -= amount + getMoneyBag(amount) + else + throw new Exception( + "You try to withdraw more money than you have on your account" + ) + ) + } + + def deposit(bag: MoneyBag): Future[Unit] = + Future { + Thread.sleep(depositWaitTime()) + synchronized { + if moneyInMoneyBag(bag) == 0 then throw new Exception("You are depositing en empty bag!") + else + balance_ += moneyIn(bag) + moneyInMoneyBag.update(bag, 0) + } + } + + def sellWaitTime(): Int + def buyWaitTime(): Int + def withdrawWaitTime(): Int + def depositWaitTime(): Int + def initialBalance(): Int + + def getMoneyBag(i: Int) = + val m = MoneyBag() + synchronized(moneyInMoneyBag.update(m, i)) + m + + def getCard(n: String): Card = + val c = Card(n) + synchronized(ownedCards.update(c, true)) + c diff --git a/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala b/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala new file mode 100644 index 0000000..a99852a --- /dev/null +++ b/previous-exams/2022-final/concpar22final03/src/test/scala/concpar22final03/Problem3Suite.scala @@ -0,0 +1,201 @@ +package concpar22final03 + +import scala.concurrent.duration.* +import scala.concurrent.{Await, Future} +import scala.util.{Try, Success, Failure} +import scala.concurrent.ExecutionContext.Implicits.global + +class Problem3Suite extends munit.FunSuite: + trait Prob3Test extends Problem3: + override val economics: EconomicsTest + class Test1 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = 10 + override def buyWaitTime() = 20 + override def depositWaitTime() = 30 + override def withdrawWaitTime() = 40 + override def initialBalance() = 0 + class Test2 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = 100 + override def buyWaitTime() = 5 + override def depositWaitTime() = 50 + override def withdrawWaitTime() = 5 + override def initialBalance() = 0 + + class Test3 extends Prob3Test: + override val economics: EconomicsTest = new EconomicsTest: + val rgen = new scala.util.Random(666) + override def sellWaitTime() = rgen.nextInt(100) + override def buyWaitTime() = rgen.nextInt(100) + override def depositWaitTime() = rgen.nextInt(100) + override def withdrawWaitTime() = rgen.nextInt(100) + override def initialBalance() = 0 + + class Test4 extends Prob3Test: + var counter = 5 + def next(): Int = + counter = counter + 5 % 119 + counter + override val economics: EconomicsTest = new EconomicsTest: + override def sellWaitTime() = next() + override def buyWaitTime() = next() + override def depositWaitTime() = next() + override def withdrawWaitTime() = next() + override def initialBalance() = next() + + def testCases = List(new Test1, new Test2) + def unevenTestCases = List(new Test3, new Test4) + + def tot(cards: List[String]): Int = + cards.map[Int]((n: String) => n.length).sum + + def testOk( + t: Prob3Test, + money: Int, + sold: List[String], + wanted: List[String] + ): Unit = + import t.* + import economics.* + val f = orderDeck(getMoneyBag(money), sold.map(getCard), wanted) + val r = Await.ready(f, 3.seconds).value.get + assert(r.isSuccess) + r match + case Success(d) => + assertEquals(d.map(_.name).sorted, wanted.sorted) + assertEquals(d.length, wanted.length) + assertEquals(isMine(d.head), true) + case Failure(e) => () + + def testFailure( + t: Prob3Test, + money: Int, + sold: List[String], + wanted: List[String] + ): Unit = + import t.* + import economics.* + val f = orderDeck(getMoneyBag(money), sold.map(getCard), wanted) + val r = Await.ready(f, 3.seconds).value.get + assert(r.isFailure) + r match + case Failure(e: NotEnoughMoneyException) => () + case _ => fail("Should have thrown a NotEnoughMoneyException exception, but did not") + + // --- Without sold cards --- + + test( + "Should work correctly when a single card is asked with enough money (no card sold) (20pts)" + ) { + testCases.foreach(t => testOk(t, 7, Nil, List("Tefeiri"))) + } + test( + "Should work correctly when a single card is asked with enough money (no card sold, uneven waiting time) (10pts)" + ) { + unevenTestCases.foreach(t => testOk(t, 7, Nil, List("Tefeiri"))) + } + test( + "Should work correctly when multiple cards are asked with enough money (no card sold) (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + testCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when multiple cards are asked with enough money (no card sold, uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + unevenTestCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when asked duplicates of cards, with enough money (no card sold) (20pts)" + ) { + val cards = List("aaaa", "aaaa", "aaaa", "dd", "dd", "dd", "dd") + testCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + test( + "Should work correctly when asked duplicates of cards, with enough money (no card sold, uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "aaaa", "aaaa", "dd", "dd", "dd", "dd") + unevenTestCases.foreach(t => testOk(t, tot(cards), Nil, cards)) + } + + // --- With sold cards --- + + test( + "Should work correctly when a single card is bought and a single of the same price is sold (20pts)" + ) { + testCases.foreach(t => testOk(t, 0, List("Chandra"), List("Tefeiri"))) + } + test( + "Should work correctly when a single card is bought and a single of the same price is sold (uneven waiting time) (10pts)" + ) { + unevenTestCases.foreach(t => testOk(t, 0, List("Chandra"), List("Tefeiri"))) + } + + test( + "Should work correctly when multiple cards are asked and multiple of matching values are sold (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("1111111", "2", "3333", "44", "55555", "666", "7777") + testCases.foreach(t => testOk(t, 0, sold, cards)) + } + test( + "Should work correctly when multiple cards are asked and multiple of matching values are sold (uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("1111111", "2", "3333", "44", "55555", "666", "7777") + unevenTestCases.foreach(t => testOk(t, 0, sold, cards)) + } + test( + "Should work correctly when multiple cards are asked and multiple of the same total value are sold (20pts)" + ) { + val cards2 = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold2 = List("111111111", "22", "3", "44", "555555", "666", "777") + assert(tot(sold2) == tot(cards2)) + testCases.foreach(t => testOk(t, 0, sold2, cards2)) + } + test( + "Should work correctly when multiple cards are asked and multiple of the same total value are sold (uneven waiting time) (10pts)" + ) { + val cards2 = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold2 = List("111111111", "22", "3", "44", "555555", "666", "777") + assert(tot(sold2) == tot(cards2)) + unevenTestCases.foreach(t => testOk(t, 0, sold2, cards2)) + } + + test( + "Should work correctly when given money and sold cards are sufficient for the wanted cards (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + testCases.foreach(t => testOk(t, bagMoney, sold, cards)) + } + test( + "Should work correctly when given money and sold cards are sufficient for the wanted cards (uneven waiting time) (10pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + unevenTestCases.foreach(t => testOk(t, bagMoney, sold, cards)) + } + + // --- Failures --- + + test( + "Should return a failure when too little money is provided (no card sold) (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + testCases.foreach(t => testFailure(t, tot(cards) - 1, Nil, cards)) + testCases.foreach(t => testFailure(t, tot(cards) - 50, Nil, cards)) + } + + test( + "Should return a failure when too little money or sold cards are provided (20pts)" + ) { + val cards = List("aaaa", "bbb", "ccccc", "dd", "eeee", "f", "ggggggg") + val sold = List("11111", "2", "33", "44", "5555", "666", "777") + val bagMoney = tot(cards) - tot(sold) + testCases.foreach(t => testFailure(t, bagMoney - 2, sold, cards)) + } diff --git a/previous-exams/2022-final/concpar22final04.md b/previous-exams/2022-final/concpar22final04.md new file mode 100644 index 0000000..0fd39f5 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04.md @@ -0,0 +1,98 @@ +# Problem 4: Implementing Spotify using actors + +## Setup + +Use the following commands to make a fresh clone of your repository: + +``` +git clone -b concpar22final04 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar22final04 +``` + +If you have issues with the IDE, try [reimporting the +build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), +if you still have problems, use `compile` in sbt instead. + +## Useful Links + +- [Akka Classic documentation](https://doc.akka.io/docs/akka/current/index-classic.html), in particular: + - [Classic Actors](https://doc.akka.io/docs/akka/current/actors.html) + - [Testing Classic Actors](https://doc.akka.io/docs/akka/current/testing.html) + - [Classic Logging](https://doc.akka.io/docs/akka/current/logging.html) +- [Akka Classic API reference](https://doc.akka.io/api/akka/current/akka/actor/index.html) +- [CS206 slides](https://gitlab.epfl.ch/lamp/cs206/-/tree/master/slides) + +## Overview + +In this exercise, you will implement the core functionalities of an online music streaming service. Users will be modelled as individual Akka actors. + +- Each user has a unique identifier and a name. +- Each user can like and unlike songs (stored in the user's _liked songs_ list). Liked songs are sorted by reverse date of liking time (the last liked song must be the first element of the list). Elements of this list must be unique: a song can only be liked once. Liking a song twice should not impact the order. +- Each user can subscribe and unsubscribe to other users to see what they are listening to. This is stored in the user's _activity feed_. The items in the activity feed are sorted by reverse date of activity time (the last added activity must be the first element of the list). Items in this list should be unique by user id. If a new activity with a user id that is already in the list is added, the former should be removed, so that we always see the latest activity for each user we have subscribed to. + +This corresponds to the core features of Spotify: + + + +Your task is to implement the receive method of the `User` actor. See the enums in the `User` what messages and responses a `User` should handle. + +You are allowed to add private methods and attributes to the `User` class. You can also import any Scala collection you might find useful. + +To implement the last part (problem 4.4), you will need to interact with the `SongStore` actor passed as the `songStore` parameter. You do not need it for the other parts. + +## Problem 4.1: Getting user info (50 points) + +Your first task is to implement the `User.receive` method so that it handles the `GetInfo` and `GetHomepageData` messages. This will allow you to pass the first 2 tests. + +## Problem 4.2: Updating user info (70 points) + +Your second task is to expand `User.receive` so that it handles the `Like` and `Unlike` messages. + +## Problem 4.3: Updating user info (70 points) + +Your third task is to expand `User.receive` so that it handles the `Subscribe`, `Unsubscribe`, `AddActivity` and `Play` messages. + +## Problem 4.4: Displaying the homepage (60 points) + +Your last (but not least!) task is to expand `User.receive` so that it handles the `GetHomepageText` message. + +A `GetHomepageText` should be answered with `HomepageText` message. Here is an example of a `HomepageText.result`: + +``` +Howdy Ada! + +Liked Songs: +* Sunny by Boney M. +* J'irai où tu iras by Céline Dion & Jean-Jacques Goldman +* Hold the line by TOTO + +Activity Feed: +* Bob is listening to Straight Edge by Minor Threat +* Donald is listening to Désenchantée by Mylène Farmer +* Carol is listening to Breakfast in America by Supertramp +``` + +More precisely, it should contains the following lines in order: + +1. `Howdy $name!`, where `$name` is the name of the recipient user. +2. A blank line +3. `Liked Songs:` +4. Zero or more lines listing the user's liked songs. Each of these lines should be of the form `"* ${song.title} by ${song.artist}`, where `${song.title}` is the title of the song and `${song.artist}` its artist. +5. A blank line +6. Zero or more lines listing the user activity feed items. Each of these lines should be of the form `* ${user.name} is listening to ${song.title} by ${song.artist}`, where `${user.name}` is the name of the user listening, `${song.title}` is the title of the song and `${song.artist}` its artist. + +In order to fetch the songs information (titles and artists), you should use the `songStore` actor passed as an argument to `User`. See the enums in the `SongsStore` companion object to learn how to interact with the song store. + +__Hint 1:__ to construct the result, you might find useful to use [`f-strings`](https://docs.scala-lang.org/overviews/core/string-interpolation.html#the-f-interpolator) and the [`List.mkString`](https://www.scala-lang.org/api/2.13.3/scala/collection/immutable/List.html#mkString(sep:String):String) method. Here is an example of how to use them: + +```scala +val fruits = List("Banana", "Apple", "Kiwi") +val result = f"""Fruits: +${fruits.map(fruit => f"* ${fruit}").mkString("\n")}""" + +assert(result == """Fruits: +* Banana +* Apple +* Kiwi""") +``` + +__Hint 2:__ if you need to send the result of a future to an actor, you should use the `pipeTo` method as described in the lectures and [here](https://doc.akka.io/docs/akka/2.5/futures.html#use-the-pipe-pattern). diff --git a/previous-exams/2022-final/concpar22final04/.gitignore b/previous-exams/2022-final/concpar22final04/.gitignore new file mode 100644 index 0000000..d094868 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/.gitignore @@ -0,0 +1,17 @@ +*.DS_Store +*.swp +*~ +*.class +*.tasty +target/ +logs/ +.bloop +.bsp +.dotty-ide-artifact +.dotty-ide.json +.idea +.metals +.vscode +*.csv +*.dat +metals.sbt diff --git a/previous-exams/2022-final/concpar22final04/assignment.sbt b/previous-exams/2022-final/concpar22final04/assignment.sbt new file mode 100644 index 0000000..70cbe95 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/assignment.sbt @@ -0,0 +1,5 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +assignmentVersion.withRank(KeyRanks.Invisible) := "39e6c8f1" + diff --git a/previous-exams/2022-final/concpar22final04/build.sbt b/previous-exams/2022-final/concpar22final04/build.sbt new file mode 100644 index 0000000..ea5fb5d --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/build.sbt @@ -0,0 +1,23 @@ +course := "concpar" +assignment := "concpar22final04" +scalaVersion := "3.1.0" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M3" % Test + +val akkaVersion = "2.6.19" +val logbackVersion = "1.2.11" +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-testkit" % akkaVersion, + // SLF4J backend + // See https://doc.akka.io/docs/akka/current/typed/logging.html#slf4j-backend + "ch.qos.logback" % "logback-classic" % logbackVersion +) +fork := true +javaOptions ++= Seq("-Dakka.loglevel=Error", "-Dakka.actor.debug.receive=on") + +val MUnitFramework = new TestFramework("munit.Framework") +testFrameworks += MUnitFramework +// Decode Scala names +testOptions += Tests.Argument(MUnitFramework, "-s") diff --git a/previous-exams/2022-final/concpar22final04/project/CourseraStudent.scala b/previous-exams/2022-final/concpar22final04/project/CourseraStudent.scala new file mode 100644 index 0000000..0d5da7f --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/CourseraStudent.scala @@ -0,0 +1,212 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scala.util.{Failure, Success, Try} +import scalaj.http._ +import play.api.libs.json.{Json, JsObject, JsPath} + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(courseId: String, key: String, partId: String, itemId: String, premiumItemId: Option[String]) + + +object CourseraStudent extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import StudentTasks.autoImport._ + import MOOCSettings.autoImport._ + import autoImport._ + + override lazy val projectSettings = Seq( + submitSetting, + ) + + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + StudentTasks.scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("<arg>").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "progfun1" => "scala-functional-programming" + case "progfun2" => "scala-functional-program-design" + case "parprog1" => "scala-parallel-programming" + case "bigdata" => "scala-spark-big-data" + case "capstone" => "scala-capstone" + case "reactive" => "scala-akka-reactive" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit <email-address> <submit-token> + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + StudentTasks.failSubmit() + } + + val base64Jar = StudentTasks.prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } + +} diff --git a/previous-exams/2022-final/concpar22final04/project/MOOCSettings.scala b/previous-exams/2022-final/concpar22final04/project/MOOCSettings.scala new file mode 100644 index 0000000..347cc6e --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/MOOCSettings.scala @@ -0,0 +1,51 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val datasetUrl = settingKey[String]("URL of the dataset used for testing") + val downloadDataset = taskKey[File]("Download the dataset required for the assignment") + val assignmentVersion = settingKey[String]("Hash string indicating the version of the assignment") + } + + import autoImport._ + + lazy val downloadDatasetDef = downloadDataset := { + val logger = streams.value.log + + datasetUrl.?.value match { + case Some(url) => + + import scalaj.http.Http + import sbt.io.IO + val dest = (Compile / resourceManaged).value / assignment.value / url.split("/").last + if (!dest.exists()) { + IO.touch(dest) + logger.info(s"Downloading $url") + val res = Http(url).method("GET") + val is = res.asBytes.body + IO.write(dest, is) + } + dest + case None => + logger.info(s"No dataset defined in datasetUrl") + throw new sbt.MessageOnlyException("No dataset to download for this assignment") + } + } + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + downloadDatasetDef, + Test / parallelExecution := false, + // Report test result after each test instead of waiting for every test to finish + Test / logBuffered := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/previous-exams/2022-final/concpar22final04/project/StudentTasks.scala b/previous-exams/2022-final/concpar22final04/project/StudentTasks.scala new file mode 100644 index 0000000..1ae03c1 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/StudentTasks.scala @@ -0,0 +1,150 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ +import scalafix.sbt.ScalafixPlugin.autoImport._ + +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + + val packageSubmission = inputKey[Unit]("package solution as an archive file") + lazy val Grading = config("grading") extend(Runtime) + } + + import autoImport._ + + // Run scalafix linting after compilation to avoid seeing parser errors twice + // Keep in sync with the use of scalafix in Grader + // (--exclude doesn't work (https://github.com/lampepfl-courses/moocs/pull/28#issuecomment-427894795) + // so we customize unmanagedSources below instead) + val scalafixLinting = Def.taskDyn { + if (new File(".scalafix.conf").exists()) { + (Compile / scalafix).toTask(" --check").dependsOn(Compile / compile) + } else Def.task(()) + } + + val testsJar = file("grading-tests.jar") + + override lazy val projectSettings = Seq( + // Run scalafix linting in parallel with the tests + (Test / test) := { + scalafixLinting.value + (Test / test).value + }, + + packageSubmissionSetting, + + fork := true, + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + scalafixConfig := { + val scalafixDotConf = (baseDirectory.value / ".scalafix.conf") + if (scalafixDotConf.exists) Some(scalafixDotConf) else None + } + ) ++ packageSubmissionZipSettings ++ ( + if(testsJar.exists) inConfig(Grading)(Defaults.testSettings ++ Seq( + unmanagedJars += testsJar, + definedTests := (Test / definedTests).value, + internalDependencyClasspath := (Test / internalDependencyClasspath).value, + managedClasspath := (Test / managedClasspath).value, + )) + else Nil + ) + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (Compile / packageSourcesOnly).value + val binaries = (Compile / packageBinWithoutResources).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None) + submission + }, + packageSourcesOnly / artifactClassifier := Some("sources"), + Compile / packageBinWithoutResources / artifact ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (Compile / resources).value.flatMap(Path.relativeTo((Compile / resourceDirectories).value)(_)) + (Compile / packageBin / mappings).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (Compile / packageSubmissionZip).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/previous-exams/2022-final/concpar22final04/project/build.properties b/previous-exams/2022-final/concpar22final04/project/build.properties new file mode 100644 index 0000000..3161d21 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.1 diff --git a/previous-exams/2022-final/concpar22final04/project/buildSettings.sbt b/previous-exams/2022-final/concpar22final04/project/buildSettings.sbt new file mode 100644 index 0000000..1d98735 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.15" \ No newline at end of file diff --git a/previous-exams/2022-final/concpar22final04/project/plugins.sbt b/previous-exams/2022-final/concpar22final04/project/plugins.sbt new file mode 100644 index 0000000..3c7aad8 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.26") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8") diff --git a/previous-exams/2022-final/concpar22final04/src/main/scala/concpar22final04/Problem4.scala b/previous-exams/2022-final/concpar22final04/src/main/scala/concpar22final04/Problem4.scala new file mode 100644 index 0000000..5e54d55 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/src/main/scala/concpar22final04/Problem4.scala @@ -0,0 +1,179 @@ +package concpar22final04 + +import akka.actor.* +import akka.testkit.* +import java.util.Date +import akka.event.LoggingReceive +import akka.pattern.* +import akka.util.Timeout +import concurrent.duration.* +import scala.concurrent.Future +import scala.concurrent.ExecutionContext + +given timeout: Timeout = Timeout(200.millis) + +/** Data associated with a song: a unique `id`, a `title` and an `artist`. + */ +case class Song(id: Int, title: String, artist: String) + +/** An activity in a user's activity feed, representing that `userRef` is + * listening to `songId`. + */ +case class Activity(userId: String, userName: String, songId: Int) + +/** Companion object of the `User` class. + */ +object User: + /** Messages that can be sent to User actors. + */ + enum Protocol: + /** Asks for a user name and id. Should be answered by a Response.Info. + */ + case GetInfo + + /** Asks home page data. Should be answered by a Response.HomepageData. + */ + case GetHomepageData + + /** Like song with id `songId`. + */ + case Like(songId: Int) + + /** Unlike song with id `songId`. + */ + case Unlike(songId: Int) + + /** Adds `subscriber` to the list of subscribers. + */ + case Subscribe(subscriber: ActorRef) + + /** Remove `subscriber` from the list of subscribers. + */ + case Unsubscribe(subscriber: ActorRef) + + /** Adds the activity `activity` to the activity feed. This message will be + * sent by the users this user has subscribed to. + */ + case AddActivity(activity: Activity) + + /** Sent when a user starts playing a song with id `songId`. The recipient + * should notify all its subscribers to update their activity feeds by + * sending them `AddActivity(Activity(...))` messages. No answer is + * expected. This message is sent by external actors. + */ + case Play(songId: Int) + + /** Asks for home page text. Should be answered by a Response.HomepageText. + */ + case GetHomepageText + + /** Responses that can be sent back from User actors. + */ + enum Responses: + /** Answer to a Protocol.GetInfo message + */ + case Info(id: String, name: String) + + /** Answer to a Protocol.GetHomepageData message + */ + case HomepageData(songIds: List[Int], activities: List[Activity]) + + /** Answer to a Protocol.GetHomepageText message + */ + case HomepageText(result: String) + +/** The `User` actor, responsible to handle `User.Protocol` messages. + */ +class User(id: String, name: String, songsStore: ActorRef) extends Actor: + import User.* + import User.Protocol.* + import User.Responses.* + import SongsStore.Protocol.* + import SongsStore.Responses.* + + given ExecutionContext = context.system.dispatcher + + /** Liked songs, by reverse date of liking time (the last liked song must + * be the first must be the first element of the list). Elements of this + * list must be unique: a song can only be liked once. Liking a song + * twice should not change the order. + */ + var likedSongs: List[Int] = List() + + /** Users who have subscribed to this users. + */ + var subscribers: Set[ActorRef] = Set() + + /** Activity feed, by reverse date of activity time (the last added + * activity must be the first element of the list). Items in this list + * should be unique by `userRef`. If a new activity with a `userRef` + * already in the list is added, the former should be removed, so that we + * always see the latest activity for each user we have subscribed to. + */ + var activityFeed: List[Activity] = List() + + + /** This actor's behavior. */ + override def receive: Receive = ??? + +/** Objects containing the messages a songs store should handle. + */ +object SongsStore: + /** Ask information about a list of songs by their ids. + */ + enum Protocol: + case GetSongs(songIds: List[Int]) + + /** List of `Song` corresponding to the list of IDs given to `GetSongs`. + */ + enum Responses: + case Songs(songs: List[Song]) + +/** A mock implementation of a songs store. + */ +class MockSongsStore extends Actor: + import SongsStore.Protocol.* + import SongsStore.Responses.* + import SongsStore.* + + val songsDB = Map( + 1 -> Song(1, "High Hopes", "Pink Floyd"), + 2 -> Song(2, "Sunny", "Boney M."), + 3 -> Song(3, "J'irai où tu iras", "Céline Dion & Jean-Jacques Goldman"), + 4 -> Song(4, "Ce monde est cruel", "Vald"), + 5 -> Song(5, "Strobe", "deadmau5"), + 6 -> Song(6, "Désenchantée", "Mylène Farmer"), + 7 -> Song(7, "Straight Edge", "Minor Threat"), + 8 -> Song(8, "Hold the line", "TOTO"), + 9 -> Song(9, "Anarchy in the UK", "Sex Pistols"), + 10 -> Song(10, "Breakfast in America", "Supertramp") + ) + + override def receive: Receive = LoggingReceive { case GetSongs(songsIds) => + sender() ! Songs(songsIds.map(songsDB)) + } + +///////////////////////// +// DEBUG // +///////////////////////// + +/** Infrastructure to help debugging. In sbt use `run` to execute this code. + * The TestKit is an actor that can send messages and check the messages it receives (or not). + */ +@main def debug() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender: + import User.* + import User.Protocol.* + import User.Responses.* + import SongsStore.Protocol.* + import SongsStore.Responses.* + + try + val songsStore = system.actorOf(Props(MockSongsStore()), "songsStore") + val anita = system.actorOf(Props(User("100", "Anita", songsStore))) + + anita ! Like(6) + expectNoMessage() // expects no message is received + + anita ! GetHomepageData + expectMsg(HomepageData(List(6), List())) + finally shutdown(system) diff --git a/previous-exams/2022-final/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala b/previous-exams/2022-final/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala new file mode 100644 index 0000000..f88d129 --- /dev/null +++ b/previous-exams/2022-final/concpar22final04/src/test/scala/concpar22final04/Problem4Suite.scala @@ -0,0 +1,361 @@ +package concpar22final04 + +import akka.actor.* +import akka.testkit.* +import akka.pattern.* +import akka.util.Timeout +import concurrent.duration.* +import User.Protocol.* +import User.Responses.* +import SongsStore.Protocol.* +import SongsStore.Responses.* +import scala.util.{Try, Success, Failure} +import com.typesafe.config.ConfigFactory +import java.util.Date +import scala.util.Random + +class Problem4Suite extends munit.FunSuite: +//--- + Random.setSeed(42178263) +/*+++ + Random.setSeed(42) +++*/ + + test("after receiving GetInfo, should answer with Info (20pts)") { + new MyTestKit: + def tests() = + ada ! GetInfo + expectMsg(Info("1", "Ada")) + } + + test("after receiving GetHomepageData, should answer with the correct HomepageData when there is no liked songs and no activity items (30pts)") { + new MyTestKit: + def tests() = + ada ! GetHomepageData + expectMsg(HomepageData(List(), List())) + } + + test("after receiving Like(1), should add 1 to the list of liked songs (20pts)") { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(1), List())) + } + + test( + "after receiving Like(1) and then Like(2), the list of liked songs should start with List(2, 1) (20pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Like(1) and then Like(1), song 1 should be in the list of liked songs only once (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(1), List())) + } + + test( + "after receiving Like(1), Like(2) and then Like(1), the list of liked songs should start with List(2, 1) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Like(1), Unlike(1) and then Unlike(1), the list of liked songs should not contain song 1 (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! Like(1) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(2, 1), List())) + } + + test( + "after receiving Subscribe(aUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser (20pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + } + + test( + "after receiving Subscribe(aUser), Subscribe(bUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + val donald = new TestProbe(system) + ada ! Subscribe(donald.ref) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + donald.expectMsg(AddActivity(Activity("1", "Ada", 5))) + } + + test( + "after receiving Subscribe(aUser), Subscribe(aUser) and then Play(5), should send AddActivity(Activity(\"1\", 5)) to aUser only once (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + expectNoMessage() + } + + test( + "after receiving Subscribe(aUser), Unsubscribe(aUser) and then Play(5), should not send AddActivity(Activity(\"1\", 5)) to aUser (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Subscribe(self) + expectNoMessage() + ada ! Play(5) + expectMsg(AddActivity(Activity("1", "Ada", 5))) + ada ! Unsubscribe(self) + expectNoMessage() + ada ! Play(5) + expectNoMessage() + } + + test( + "after receiving AddActivity(Activity(\"1\", 5)), Activity(\"1\", 5) should be in the activity feed (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! AddActivity(Activity("0", "Self", 5)) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(), List(Activity("0", "Self", 5)))) + } + + test( + "after receiving AddActivity(Activity(\"1\", 5)) and AddActivity(Activity(\"1\", 6)), Activity(\"1\", 6) should be in the activity feed and Activity(\"1\", 5) should not (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! AddActivity(Activity("0", "Self", 5)) + expectNoMessage() + ada ! AddActivity(Activity("0", "Self", 6)) + expectNoMessage() + ada ! GetHomepageData + expectMsg(HomepageData(List(), List(Activity("0", "Self", 6)))) + } + + test( + "after receiving GetHomepageText, should answer with a result containing \"Howdy $name!\" where $name is the user's name (10pts)" + ) { + new MyTestKit: + def tests() = + val name = Random.alphanumeric.take(5).mkString + val randomUser = system.actorOf(Props(classOf[User], "5", name, songsStore), "user-5") + randomUser ! GetHomepageText + expectMsgClass(classOf[HomepageText]).result.contains(f"Howdy $name!") + } + + test( + "after receiving GetHomepageText, should answer with the correct names of liked songs (1) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(8) + expectNoMessage() + ada ! Like(3) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(2) + .take(4) + .mkString("\n") + .trim, + """ + |Liked Songs: + |* Sunny by Boney M. + |* J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + |* Hold the line by TOTO + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct names of liked songs (2) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(9) + expectNoMessage() + ada ! Like(7) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(2) + .take(3) + .mkString("\n") + .trim, + """ + |Liked Songs: + |* Straight Edge by Minor Threat + |* Anarchy in the UK by Sex Pistols + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct activity feed (1) (10pts)" + ) { + new MyTestKit: + def tests() = + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + bob ! Play(3) + expectNoMessage() + carol ! Play(8) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(4) + .take(10) + .mkString("\n") + .trim, + """ + |Activity Feed: + |* Carol is listening to Hold the line by TOTO + |* Bob is listening to J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct activity feed (2) (10pts)" + ) { + new MyTestKit: + def tests() = + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + bob ! Play(9) + expectNoMessage() + carol ! Play(10) + expectNoMessage() + donald ! Play(6) + expectNoMessage() + bob ! Play(7) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .drop(4) + .take(10) + .mkString("\n") + .trim, + """ + |Activity Feed: + |* Bob is listening to Straight Edge by Minor Threat + |* Donald is listening to Désenchantée by Mylène Farmer + |* Carol is listening to Breakfast in America by Supertramp + """.stripMargin.trim + ) + } + + test( + "after receiving GetHomepageText, should answer with the correct text (full test) (10pts)" + ) { + new MyTestKit: + def tests() = + ada ! Like(1) + expectNoMessage() + ada ! Like(2) + expectNoMessage() + bob ! Subscribe(ada) + expectNoMessage() + carol ! Subscribe(ada) + expectNoMessage() + donald ! Subscribe(ada) + expectNoMessage() + donald ! Play(3) + expectNoMessage() + bob ! Play(4) + expectNoMessage() + carol ! Play(5) + expectNoMessage() + ada ! GetHomepageText + assertEquals( + expectMsgClass(classOf[HomepageText]).result.linesIterator + .mkString("\n") + .trim, + """ + |Howdy Ada! + | + |Liked Songs: + |* Sunny by Boney M. + |* High Hopes by Pink Floyd + | + |Activity Feed: + |* Carol is listening to Strobe by deadmau5 + |* Bob is listening to Ce monde est cruel by Vald + |* Donald is listening to J'irai où tu iras by Céline Dion & Jean-Jacques Goldman + """.stripMargin.trim + ) + } + + abstract class MyTestKit + extends TestKit(ActorSystem("TestSystem")) + with ImplicitSender: + val songsStore = system.actorOf(Props(MockSongsStore()), "songsStore") + def makeAda() = system.actorOf(Props(classOf[User], "1", "Ada", songsStore), "user-1") + val ada = makeAda() + val bob = system.actorOf(Props(classOf[User], "2", "Bob", songsStore), "user-2") + val carol = system.actorOf(Props(classOf[User], "3", "Carol", songsStore), "user-3") + val donald = system.actorOf(Props(classOf[User], "4", "Donald", songsStore), "user-4") + def tests(): Unit + try tests() + finally shutdown(system) diff --git a/previous-exams/2022-final/spotify.jpg b/previous-exams/2022-final/spotify.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79f91ad367a09ab3348ff0c167eaa9ddbdba8f37 GIT binary patch literal 343099 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 literal 0 HcmV?d00001 -- GitLab