Initial commit
This commit is contained in:
commit
1790b94b6e
6
build.sbt
Normal file
6
build.sbt
Normal 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")
|
||||||
|
|
21
src/main/scala/Account.scala
Normal file
21
src/main/scala/Account.scala
Normal 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
28
src/main/scala/Bank.scala
Normal 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
12
src/main/scala/Main.scala
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
src/main/scala/Transaction.scala
Normal file
59
src/main/scala/Transaction.scala
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
5
src/main/scala/exceptions/IllegalAmountException.scala
Normal file
5
src/main/scala/exceptions/IllegalAmountException.scala
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package exceptions
|
||||||
|
|
||||||
|
class IllegalAmountException (message: String = null, cause: Throwable = null) extends RuntimeException(message, cause) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package exceptions
|
||||||
|
|
||||||
|
class NoSufficientFundsException(message: String = null, cause: Throwable = null) extends RuntimeException(message, cause) {
|
||||||
|
|
||||||
|
}
|
199
src/test/scala/AccountTests.scala
Normal file
199
src/test/scala/AccountTests.scala
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user