Initial commit

main
Oystein Kristoffer Tveit 2022-10-14 12:51:19 +02:00
commit 1790b94b6e
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
8 changed files with 335 additions and 0 deletions

6
build.sbt Normal file
View File

@ -0,0 +1,6 @@
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.0" % "test"
resolvers += "Central" at "https://central.maven.org/maven2/"
scalacOptions := Seq("-unchecked", "-deprecation", "-feature", "-language:postfixOps")

View File

@ -0,0 +1,21 @@
import exceptions._
class Account(val bank: Bank, initialBalance: Double) {
class Balance(var amount: Double) {}
val balance = new Balance(initialBalance)
// TODO
// for project task 1.2: implement functions
// for project task 1.3: change return type and update function bodies
def withdraw(amount: Double): Unit = ???
def deposit (amount: Double): Unit = ???
def getBalanceAmount: Double = ???
def transferTo(account: Account, amount: Double) = {
bank addTransactionToQueue (this, account, amount)
}
}

28
src/main/scala/Bank.scala Normal file
View File

@ -0,0 +1,28 @@
class Bank(val allowedAttempts: Integer = 3) {
private val transactionsQueue: TransactionQueue = new TransactionQueue()
private val processedTransactions: TransactionQueue = new TransactionQueue()
def addTransactionToQueue(from: Account, to: Account, amount: Double): Unit = ???
// TODO
// project task 2
// create a new transaction object and put it in the queue
// spawn a thread that calls processTransactions
private def processTransactions: Unit = ???
// TOO
// project task 2
// Function that pops a transaction from the queue
// and spawns a thread to execute the transaction.
// Finally do the appropriate thing, depending on whether
// the transaction succeeded or not
def addAccount(initialBalance: Double): Account = {
new Account(this, initialBalance)
}
def getProcessedTransactionsAsList: List[Transaction] = {
processedTransactions.iterator.toList
}
}

12
src/main/scala/Main.scala Normal file
View File

@ -0,0 +1,12 @@
object Main extends App {
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start
t
}
}

View File

@ -0,0 +1,59 @@
import exceptions._
import scala.collection.mutable
object TransactionStatus extends Enumeration {
val SUCCESS, PENDING, FAILED = Value
}
class TransactionQueue {
// TODO
// project task 1.1
// Add datastructure to contain the transactions
// Remove and return the first element from the queue
def pop: Transaction = ???
// Return whether the queue is empty
def isEmpty: Boolean = ???
// Add new element to the back of the queue
def push(t: Transaction): Unit = ???
// Return the first element from the queue without removing it
def peek: Transaction = ???
// Return an iterator to allow you to iterate over the queue
def iterator: Iterator[Transaction] = ???
}
class Transaction(val transactionsQueue: TransactionQueue,
val processedTransactions: TransactionQueue,
val from: Account,
val to: Account,
val amount: Double,
val allowedAttemps: Int) extends Runnable {
var status: TransactionStatus.Value = TransactionStatus.PENDING
var attempt = 0
override def run: Unit = {
def doTransaction() = {
// TODO - project task 3
// Extend this method to satisfy requirements.
from withdraw amount
to deposit amount
}
// TODO - project task 3
// make the code below thread safe
if (status == TransactionStatus.PENDING) {
doTransaction
Thread.sleep(50) // you might want this to make more room for
// new transactions to be added to the queue
}
}
}

View File

@ -0,0 +1,5 @@
package exceptions
class IllegalAmountException (message: String = null, cause: Throwable = null) extends RuntimeException(message, cause) {
}

View File

@ -0,0 +1,5 @@
package exceptions
class NoSufficientFundsException(message: String = null, cause: Throwable = null) extends RuntimeException(message, cause) {
}

View File

@ -0,0 +1,199 @@
import org.scalatest.FunSuite
import exceptions._
class AccountTests extends FunSuite {
val bank = new Bank()
test("Test 01: Valid account withdrawal") {
val acc = new Account(bank, 500)
val result = acc.withdraw(250)
assert(acc.getBalanceAmount == 250)
assert(result.isLeft)
}
test("Test 02: Invalid account withdrawal should throw exception") {
val acc = new Account(bank, 500)
val result = acc.withdraw(750)
assert(acc.getBalanceAmount == 500)
assert(result.isRight)
}
test("Test 03: Withdrawal of negative amount should throw exception") {
val acc = new Account(bank, 500)
val result = acc.withdraw(-100)
assert(acc.getBalanceAmount == 500)
assert(result.isRight)
}
test("Test 04: Valid account deposit") {
val acc = new Account(bank, 500)
val result = acc.deposit(250)
assert(acc.getBalanceAmount == 750)
assert(result.isLeft)
}
test("Test 05: Deposit of negative amount should throw exception") {
val acc = new Account(bank, 500)
val result = acc.deposit(-50)
assert(acc.getBalanceAmount == 500)
assert(result.isRight)
}
test("Test 06: Correct balance amount after several withdrawals and deposits") {
val acc = new Account(bank, 50000)
val first = Main.thread {
for (i <- 0 until 100) {
acc.withdraw(10); Thread.sleep(10)
}
}
val second = Main.thread {
for (i <- 0 until 100) {
acc.deposit(5); Thread.sleep(20)
}
}
val third = Main.thread {
for (i <- 0 until 100) {
acc.withdraw(50); Thread.sleep(10)
}
}
val fourth = Main.thread {
for (i <- 0 until 100) {
acc.deposit(100); Thread.sleep(10)
}
}
first.join()
second.join()
third.join()
fourth.join()
assert(acc.getBalanceAmount == 54500)
}
}
class AccountTransferTests extends FunSuite {
test("Test 07: Valid transfer between accounts") {
val bank = new Bank()
val acc1 = bank.addAccount(100)
val acc2 = bank.addAccount(200)
acc1 transferTo(acc2, 50)
while (bank.getProcessedTransactionsAsList.size != 1) {
Thread.sleep(100)
}
assert(bank.getProcessedTransactionsAsList.last.status == TransactionStatus.SUCCESS)
assert((acc1.getBalanceAmount == 50) && (acc2.getBalanceAmount == 250))
}
test("Test 08: Transfer of negative amount between accounts should fail") {
val bank = new Bank()
val acc1 = bank.addAccount(500)
val acc2 = bank.addAccount(1000)
acc1 transferTo(acc2, -100)
while (bank.getProcessedTransactionsAsList.size != 1) {
Thread.sleep(100)
}
assert(bank.getProcessedTransactionsAsList.last.status == TransactionStatus.FAILED)
assert((acc1.getBalanceAmount == 500) && (acc2.getBalanceAmount == 1000))
}
test("Test 09: Invalid transfer between accounts due to insufficient funds should lead to transaction status FAILED and no money should be transferred between accounts") {
val bank = new Bank()
val acc1 = new Account(bank, 100)
val acc2 = new Account(bank, 1000)
acc1 transferTo(acc2, 150)
while (bank.getProcessedTransactionsAsList.size != 1) {
Thread.sleep(100)
}
assert(bank.getProcessedTransactionsAsList.last.status == TransactionStatus.FAILED)
assert((acc1.getBalanceAmount == 100) && (acc2.getBalanceAmount == 1000))
}
test("Test 10: Correct balance amounts after several transfers") {
val bank = new Bank()
val acc1 = new Account(bank, 3000)
val acc2 = new Account(bank, 5000)
val first = Main.thread {
for (i <- 0 until 100) {
bank addTransactionToQueue(acc1, acc2, 30)
}
}
val second = Main.thread {
for (i <- 0 until 100) {
bank addTransactionToQueue(acc2, acc1, 23)
}
}
first.join()
second.join()
while (bank.getProcessedTransactionsAsList.size != 200) {
Thread.sleep(100)
}
assert((acc1.getBalanceAmount == 2300) && (acc2.getBalanceAmount == 5700))
}
test("Test 11: Failed transactions should retry and potentially succeed with multiple allowed attempts") {
var failed = 0
for (x <- 1 to 100) {
val bank = new Bank(allowedAttempts = 3)
val acc1 = new Account(bank, 100)
val acc2 = new Account(bank, 100)
val acc3 = new Account(bank, 100)
for (i <- 1 to 6) { acc1 transferTo (acc2, 50) }
for (j <- 1 to 2) { acc3 transferTo (acc1, 50) }
while (bank.getProcessedTransactionsAsList.size != 8) {
Thread.sleep(100)
}
if (!(acc1.getBalanceAmount == 0
&& acc2.getBalanceAmount == 300
&& acc3.getBalanceAmount == 0)) failed += 1
}
assert(failed <= 5)
}
test("Test 12: Some transactions should be stopped with only one allowed attempt") {
var failed = 0
for (x <- 1 to 100) {
val bank = new Bank(allowedAttempts = 1)
val acc1 = new Account(bank, 100)
val acc2 = new Account(bank, 100)
val acc3 = new Account(bank, 100)
for (i <- 1 to 6) { acc1 transferTo (acc2, 50) }
for (j <- 1 to 2) { acc3 transferTo (acc1, 50) }
while (bank.getProcessedTransactionsAsList.size != 8) {
Thread.sleep(100)
}
if (!(acc2.getBalanceAmount != 300 && acc3.getBalanceAmount == 0)) failed += 1
}
assert(failed <= 5)
}
}