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 all commits
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
53 changes: 53 additions & 0 deletions exercises/bank-account/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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.

## Instructions

Run the test file, and fix each of the errors in turn. When you get the
first test to pass, go to the first pending or skipped test, and make
that pass as well. When all of the tests are passing, feel free to
submit.

Remember that passing code is just the first step. The goal is to work
towards a solution that is as readable and expressive as you can make
it.

Have fun!

## Exception messages

Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
a message.

To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
`raise Exception`, you shold write:

```python
raise Exception("Meaningful message indicating the source of the error")
```

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bank-account` directory.

You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.

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 interrupted
# by thread switch, thus testing synchronization effectively
try:
sys.setswitchinterval(1e-12)
except AttributeError:
# For Python 2 compatibility
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