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