A document from MCS 260 Fall 2021, instructor Emily Dumas.
You can also get the notebook file.

- Course instructor: Emily Dumas
- Solutions prepared by: Emily Dumas and Jennifer Vaccaro (Fall 2020 MCS 260 TA) and Kylash Viswanathan

This worksheet focuses on **regular expressions**, **software licensing**, and **testing with pytest**.

The main resources to refer to for this worksheet are:

- pythex.org or one of these other regular expression editor/debugger tools:
- regex101.com (need to click "Python" under the "flavor" menu at left)
- pyregex.com

- Sample programs
- Lecture 30 - Regular expressions
- Lecture 31 - Regular expressions 2 & software licensing
- Lecture 32 - Testing and pytest
- Downey's book

(Lecture videos are not linked on worksheets, but are also useful to review while working on worksheets. Video links can be found in the course course Blackboard site.)

In each part, if a description is given, write a regular expression to match it. It a regular expression is given, write a description of what it matches.

Matches the string `fact`

, optionally followed by an underscore and another word consisting of latin alphabet letters, followed by a pair of parentheses with an integer between them. No spaces are permitted anywhere.

`fact(_[A-Za-z]+)?\(\d+\)`

**Discussion:** We use `\(`

or `\)`

to match an actual parenthesis** as opposed to `(`

and `)`

which create groups.
Matches `fact_recursive(5)`

or `fact(15)`

or `fact_iterative(0)`

, but does not match `fact()`

or `fact_ (2)`

or `fact_recursive( 67 )`

.

`(xx)+x([yz][yz])*`

Matches an odd number of `x`

s, requiring at least three to be present, followed by an even number of letters that are all `y`

or `z`

. It is permissible to not have any `y`

or `z`

there at all.

**Discussion:** This would match `xxxzy`

or `xxxxx`

or `xxxzzyyzy`

but not `x`

or `xyz`

or `xxxxyy`

. Note that the highlighter on pythex.org will highlight the first match it finds, and ignore any other match that overlaps with it. So if you test this expression with string `xxxxyz`

, it will highlight the initial `xxx`

match, even though there is also `xxxyz`

later in the string which matches as well (but overlaps the first one).

Match the recommended style for Python variable names: A string consisting of lower case letters, underscores, and digits, and which doesn't start with a digit or underscore.

For example, all of these should match:

`number_of_angry_pandas`

`draft91final_modified`

`i`

`res`

And NONE of these should match:

`_whatever`

`5november`

`오징어`

`AbstractJavaCourseFactory`

`M`

`[a-z][A-Za-z0-9_]*`

`[Mm]on(day)?|[Tt]ue(s(day)?)?|[Ww]ed(nesday)|[Tt]hu(r(s(day)?)?)?|[Ff]ri(day)`

(Hint: the intent is probably clear; but what's going on with all the nested parentheses and question marks? Does `Thusday`

match? Does `Thur`

? Why or why not?)

Matches the name of one day of the week in English, capitalized or not, allowing the abbreviations

- Mon
- Tue
- Tues
- Wed
- Thu
- Thur
- Thurs
- Fri

The nested parentheses handle the logic to allow certain abbreviations. For example, in `Thursday`

it's optional to have anything after the `u`

, but if something is there, it should begin with `r`

and optionally continue. If it continues, the next letter should be `s`

, optionally followed by `day`

.

Match a Canadian postcode, which consists of a capital letter, a digit, a capital letter, a space, a digit, a capital letter, and another digit.

e.g. `K8N 5W6`

and `V8K 2S0`

match while `A8K2L3`

and `K8n 5W6`

and `1N2 S74`

and `ASK DAD`

do not match.

`[A-Z]\d[A-Z] \d[A-Z]\d`

`\(\s*[0-9]+(\s*,\s*[0-9]+)\s*\)`

A list of integers, separated by commas and optionally spaces, and surrounded by parentheses. Must have at least one integer present (e.g. `(58)`

or `(1,2, 3, 4 , 5)`

matches but `()`

or `2,3`

does not).

`geometry`

module¶Here is a link to the `geometry.py`

module we developed in a previous lecture. It allows you to represent Circles, Rectangles, and Squares in the plane and to perform some operations on them.

Download this file and save it in its own folder (e.g. `geomtesting`

would be a good name for the folder).

Now, create a file `test_geometry.py`

in the same folder. In that file, create at least 4 functions whose names begin with `test_`

that perform tests on the classes in `test_geometry.py`

. Running this script with the python interpreter should do nothing, since it should only declare test functions, and should not call them.

In a terminal opened to the same directory containing `test_geometry.py`

, run pytest with

`python3 -m pytest`

and confirm that all of your tests are discovered, and that they all pass.

Examples of suggested tests:

- two Circle objects created to have the same center an radius compare as equal
- two Rectangle objects created to have the same center, width, and height compare as equal
- using the
`.scale()`

method of a specific Rectangle you choose for testing purposes gives the same results as what you expect based on computing by hand. - adding two example rectangles gives the same bounding box as you compute by hand.

Note that the equality test for objects in `geometry.py`

is potentially quite fragile, because it tests floats for equality. One way to avoid problems with floating point error is to choose examples where every value involved (width, height, center coordinates, radius, scale factor, etc.) is actually an integer.

In [ ]:

```
# Contents of `test_geometry.py`
# Based on Fall 2020 solution by Jennifer Vaccaro
'''pytest test functions for the module geometry'''
import geometry
def test_circleeq():
'''Confirms that two circles with the same
center and radius are equal'''
C1 = geometry.Circle(1, 2, 5)
C2 = geometry.Circle(1, 2, 5)
assert C1 == C2
def test_circleineq1():
'''Confirms that two circles with differing
centers are not equal'''
C1 = geometry.Circle(1, 2, 5)
C2 = geometry.Circle(0, 2, 5)
assert C1 != C2
def test_circleineq2():
'''Confirms that two circles with differing
centers are not equal'''
C1 = geometry.Circle(1, 2, 5)
C2 = geometry.Circle(1, 3, 5)
assert C1 != C2
def test_circleineq3():
'''Confirms that two circles with differing
radii are not equal'''
C1 = geometry.Circle(1, 2, 5)
C2 = geometry.Circle(1, 2, 89)
assert C1 != C2
def test_circlescale():
'''Confirms that the circle scaling
behaves as expected'''
C = geometry.Circle(5,5,5)
C.scale(3)
assert C.cx == 5
assert C.cy == 5
assert C.r == 15
def test_circletranslate():
'''Confirms that circle translating
behaves as expected.'''
C = geometry.Circle(5,5,5)
C.translate(4,3)
assert C.cx == 9
assert C.cy == 8
assert C.r == 5
def test_recteq():
'''Confirms that two rectangles with the
same centers and sidelengths are equal'''
R1 = geometry.Rectangle(1,-3, 3, 2)
R2 = geometry.Rectangle(1,-3, 3, 2)
assert R1 == R2
def test_rectineq1():
'''Confirms that two rectangles that differ
in center or sidelength are not equal'''
R1 = geometry.Rectangle(1,-3, 3, 2)
R2 = geometry.Rectangle(2,-3, 3, 2)
assert R1 != R2
def test_rectineq2():
'''Confirms that two rectangles that differ
in center or sidelength are not equal'''
R1 = geometry.Rectangle(1,-3, 3, 2)
R2 = geometry.Rectangle(1,-4, 3, 2)
assert R1 != R2
def test_rectineq3():
'''Confirms that two rectangles that differ
in center or sidelength are not equal'''
R1 = geometry.Rectangle(1,-3, 3, 2)
R2 = geometry.Rectangle(1,-3, 5, 2)
assert R1 != R2
def test_rectineq4():
'''Confirms that two rectangles that differ
in center or sidelength are not equal'''
R1 = geometry.Rectangle(1,-3, 3, 2)
R2 = geometry.Rectangle(1,-3, 3, 1)
assert R1 != R2
def test_rectscale():
'''Confirms that the rectangle scaling
behaves as expected'''
R = geometry.Rectangle(1,1,2,2)
R.scale(2)
assert R.w == 4
assert R.h == 4
assert R.cx == 1
assert R.cy == 1
def test_bbox():
'''Confirms sum of rectangles gives
axis-aligned bounding box for this example:
(1,12) (23,12)
┌─────────────────────┐
│ │
│ (8,9) (18,9) │
│ ┌─────────┐ │
│ │ │ │
│ │ │ │
│ R2 │ │ │
│ │ │ │
└──────┼─────────┼────┘
(1,4) │ │ (23,4)
│ R1 │
└─────────┘
(8,1) (18,1)
'''
R1 = geometry.Rectangle(13,5,10,8)
R2 = geometry.Rectangle(12,8,22,8)
assert R1 + R2 == geometry.Rectangle(12,6.5,22,11)
# No need for any code outside of the functions to call them! Pytest will run them for us.
```

Write a program `intcalls.py`

that takes a filename as a command line argument. It should read the file and look for places where a Python function is called that meet all of the following characteristics:

- The name of the function is a single lower-case word (letters a-z)
- There is no space between the function name and its parentheses
- Inside the parentheses is an integer, possibly surrounded by spaces

For each match it finds, the match text and the line number should be printed.

For example, if you run `python3 intcalls.py findphone.py`

where `findphone.py`

is the example program from lecture, the output should be:

```
Line 8: exit(1)
Line 15: group(1)
Line 16: group(2)
Line 17: group(3)
```

In [ ]:

```
# Contents of intcalls.py
# MCS 260 Fall 2021 Worksheet 12
import re
import sys
if len(sys.argv)<2:
print("Usage: {} INPUTFILE".format(sys.argv[0]))
f = open(sys.argv[1],"r",encoding="UTF-8")
for i,line in enumerate(f):
for m in re.finditer(r"[a-z]+\(\s*\d+\s*\)",line):
print("Line {}: {}".format(i,m.group()))
```

The MIT License is a popular and very permission open-source software license. Find a copy of the MIT License as a text file, and save it in the directory where you are doing your MCS 260 project work under the name `LICENSE.txt`

. Imagine you are applying it to a piece of software you've written. Edit `LICENSE.txt`

to fill in the relevant info. (The license you download is really a template, and has blank spaces for certain things like your name and the date the software was created.)

*- Content of practice LICENSE.txt -*

Copyright 2021 Emily Dumas

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

- 2021-11-11 Initial release