Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement exercise bank-account #1260

Merged
merged 5 commits into from
Apr 3, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,18 @@
"transforming"
]
},
{
"uuid": "83a3ff95-c043-401c-bc2c-547d52344b02",
"slug": "bank-account",
"core": false,
"unlocked_by": null,
"difficulty": 4,
"topics": [
"classes",
"concurrency",
"conditionals"
]
},
{
"uuid": "d41238ce-359c-4a9a-81ea-ca5d2c4bb50d",
"slug": "twelve-days",
Expand Down
21 changes: 21 additions & 0 deletions exercises/bank-account/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Bank Account
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use configlet to generate README files for exercises.


Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. Watch out for concurrent transactions!

A bank account can be accessed in multiple ways. Clients can make deposits and withdrawals using the internet, mobile phones, etc. Shops can charge against the account.

Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language).

It should be possible to close an account; operations against a closed account must fail.

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.

For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.

For more detailed information about running tests, code style and linting,
please see the [help page](http://exercism.io/languages/python).

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
18 changes: 18 additions & 0 deletions exercises/bank-account/bank_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class BankAccount(object):
def __init__(self):
pass

def get_balance(self):
pass

def open(self):
pass

def deposit(self, amount):
pass

def withdraw(self, amount):
pass

def close(self):
pass
120 changes: 120 additions & 0 deletions exercises/bank-account/bank_account_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import sys
import threading
import time
import unittest

from bank_account import BankAccount


class BankAccountTests(unittest.TestCase):

def setUp(self):
self.account = BankAccount()

def test_newly_opened_account_has_zero_balance(self):
self.account.open()
self.assertEqual(self.account.get_balance(), 0)

def test_can_deposit_money(self):
self.account.open()
self.account.deposit(100)
self.assertEqual(self.account.get_balance(), 100)

def test_can_deposit_money_sequentially(self):
self.account.open()
self.account.deposit(100)
self.account.deposit(50)

self.assertEqual(self.account.get_balance(), 150)

def test_can_withdraw_money(self):
self.account.open()
self.account.deposit(100)
self.account.withdraw(50)

self.assertEqual(self.account.get_balance(), 50)

def test_can_withdraw_money_sequentially(self):
self.account.open()
self.account.deposit(100)
self.account.withdraw(20)
self.account.withdraw(80)

self.assertEqual(self.account.get_balance(), 0)

def test_checking_balance_of_closed_account_throws_error(self):
self.account.open()
self.account.close()

with self.assertRaises(ValueError):
self.account.get_balance()

def test_deposit_into_closed_account(self):
self.account.open()
self.account.close()

with self.assertRaises(ValueError):
self.account.deposit(50)

def test_withdraw_from_closed_account(self):
self.account.open()
self.account.close()

with self.assertRaises(ValueError):
self.account.withdraw(50)

def test_cannot_withdraw_more_than_deposited(self):
self.account.open()
self.account.deposit(25)

with self.assertRaises(ValueError):
self.account.withdraw(50)

def test_cannot_withdraw_negative(self):
self.account.open()
self.account.deposit(100)

with self.assertRaises(ValueError):
self.account.withdraw(-50)

def test_cannot_deposit_negative(self):
self.account.open()

with self.assertRaises(ValueError):
self.account.deposit(-50)

def test_can_handle_concurrent_transactions(self):
self.account.open()
self.account.deposit(1000)

for _ in range(10):
self.adjust_balance_concurrently()

def adjust_balance_concurrently(self):
def transact():
self.account.deposit(5)
time.sleep(0.001)
self.account.withdraw(5)

# Greatly improve the chance of an operation being interuppted
# by thread switch, thus testing synchronization effectively
try:
sys.setswitchinterval(1e-12)
except AttributeError:
# Python 2 compatible
sys.setcheckinterval(1)

threads = []
for _ in range(1000):
t = threading.Thread(target=transact)
threads.append(t)
t.start()

for thread in threads:
thread.join()

self.assertEqual(self.account.get_balance(), 1000)


if __name__ == '__main__':
unittest.main()
35 changes: 35 additions & 0 deletions exercises/bank-account/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import threading


class BankAccount(object):
def __init__(self):
self.is_open = False
self.balance = 0
self.lock = threading.Lock()

def get_balance(self):
with self.lock:
if self.is_open:
return self.balance
else:
raise ValueError

def open(self):
self.is_open = True

def deposit(self, amount):
with self.lock:
if self.is_open and amount > 0:
self.balance += amount
else:
raise ValueError

def withdraw(self, amount):
with self.lock:
if self.is_open and 0 < amount <= self.balance:
self.balance -= amount
else:
raise ValueError

def close(self):
self.is_open = False