From 55efa4a97ab5d550c8a69a34102afab850daf982 Mon Sep 17 00:00:00 2001 From: potato Date: Wed, 6 Nov 2019 22:44:38 -0600 Subject: [PATCH 001/180] version changes --- Monika After Story/game/definitions.rpy | 2 +- Monika After Story/game/options.rpy | 2 +- Monika After Story/game/screens.rpy | 19 +- Monika After Story/game/updater.rpy | 13 +- Monika After Story/game/zz_dump.rpy | 317 ++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 14 deletions(-) create mode 100644 Monika After Story/game/zz_dump.rpy diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 41e2d081f6..870ed7013c 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -3409,7 +3409,7 @@ init -990 python in mas_utils: # unstable should never delete logs - if store.persistent._mas_unstable_mode: + if store.persistent._mas_unstable_mode or mas_r7_mode: mas_log = getMASLog("log/mas_log", append=True, flush=True) else: mas_log = getMASLog("log/mas_log") diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 50d78e121b..ec4c14d8a5 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -23,7 +23,7 @@ define gui.show_name = False ## The version of the game. -define config.version = "0.10.3" +define config.version = "0.10.3-r7-2019.11.06.1" ## Text that is placed on the game's about screen. To insert a blank line ## between paragraphs, write \n\n. diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index db6a3e45b4..316ee0690e 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -1135,16 +1135,17 @@ screen preferences(): vbox: style_prefix ("check" if not mas_globals.dark_mode else "check_dark" ) label _("Gameplay") - if persistent._mas_unstable_mode: - textbutton _("Unstable"): - action SetField(persistent, "_mas_unstable_mode", False) - selected persistent._mas_unstable_mode - else: - textbutton _("Unstable"): - action [Show(screen="dialog", message=layout.UNSTABLE, ok_action=Hide(screen="dialog")), SetField(persistent, "_mas_unstable_mode", True)] - selected persistent._mas_unstable_mode - hovered tooltip.Action(layout.MAS_TT_UNSTABLE) +# if persistent._mas_unstable_mode: +# textbutton _("Unstable"): +# action SetField(persistent, "_mas_unstable_mode", False) +# selected persistent._mas_unstable_mode +# +# else: +# textbutton _("Unstable"): +# action [Show(screen="dialog", message=layout.UNSTABLE, ok_action=Hide(screen="dialog")), SetField(persistent, "_mas_unstable_mode", True)] +# selected persistent._mas_unstable_mode +# hovered tooltip.Action(layout.MAS_TT_UNSTABLE) textbutton _("Repeat Topics"): action ToggleField(persistent,"_mas_enable_random_repeats", True, False) diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index 8ce6e67546..caa72468fc 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -1,3 +1,6 @@ +# r7 mode +define mas_r7_mode = True + # enabling unstable mode default persistent._mas_unstable_mode = False default persistent._mas_can_update = True @@ -12,6 +15,7 @@ default persistent._mas_just_updated = False # new s3 links define mas_updater.regular = "http://d2vycydjjutzqv.cloudfront.net/updates.json" define mas_updater.unstable = "http://dzfsgufpiee38.cloudfront.net/updates.json" +define mas_updater.r7 = "http://d1j8x24k8p6koi.cloudfront.net/updates.json" define mas_updater.force = False define mas_updater.timeout = 10 # timeout default @@ -528,7 +532,7 @@ init -1 python: return # old version check - if persistent._mas_unstable_mode: + if mas_r7_mode: # rpartion the ., the last item should be build number lv_build_number = store.mas_utils.tryparseint( latest_version.rpartition(".")[2], @@ -749,7 +753,10 @@ init python in mas_updater: curr_time = time.time() - if renpy.game.persistent._mas_unstable_mode: + if mas_r7_mode: + update_link = r7 + + elif renpy.game.persistent._mas_unstable_mode: update_link = unstable else: @@ -970,7 +977,7 @@ label update_now: $import time #this instance of time can stay # steam check - if persistent.steam and not persistent._mas_unstable_mode: + if persistent.steam: return # screen check diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy new file mode 100644 index 0000000000..a9abdff8af --- /dev/null +++ b/Monika After Story/game/zz_dump.rpy @@ -0,0 +1,317 @@ +## dumps file for unstablers + +init 999 python: + + + def mas_eventDataDump(): + """ + Data dump for purely events stats + """ + import os + from store.evhand import event_database,farewell_database,greeting_database + from store.mas_moods import mood_db + from store.mas_stories import story_database + + try: + mas_all_ev_db.values() + except: + # we dont have access to the main db + # just drop out for now + return + + # setup filepath + _ev_stats = "/ev_dump.log" + _ev_stats_fp = (config.basedir + _ev_stats).replace("\\", "/") + + # setup counting + class StatCounter(object): + """ + Temp class to work with stats + """ + + + # setup lines + _ev_finalstatline = ( + "\n---ST ({12}) ---\n" + + "EV:{0}\n" + + "PC:{1}\n" + + "RC:{2}\n" + + "UC:{3}\n" + + "LC:{4}\n" + + "SC:{5}\n" + + "SSC:{6} - AVG:{7}\n" + + "PSC:{8} - AVG:{9}\n" + + "RSC:{10} - AVG:{11}\n" + ) + _ev_statline = "{0} - p:{1} - r:{2} - u:{3} - s:{4} - sc:{5}\n" + + + def __init__(self, name, db): + """ + IN: + name - display name for this database of stats + db - the database this stats is associatd with + """ + self.ev_count = 0 + self.pool_count = 0 + self.rand_count = 0 + self.unlo_count = 0 + self.lock_count = 0 + self.seen_count = 0 + self.show_count = 0 + self.rshow_count = 0 + self.pshow_count = 0 + self.most_seen_ev = None + self.name = name + self.db = db + + + def _calcAvg(self, num, den): + """ + average calculator, with built in N/A + """ + if den == 0: + return "N/A" + + return num / float(den) + + + def calcAvgs(self): + """ + Calculates averages + + Returns tuple: + [0]: show count avg + [1]: pool show count avg + [2]: rand show count avg + """ + return ( + self._calcAvg(self.show_count, self.ev_count), + self._calcAvg(self.pshow_count, self.pool_count), + self._calcAvg(self.rshow_count, self.rand_count) + ) + + + def checkAndReplaceMostSeen(self, ev): + """ + Checks and replaces most seen if necessary + + IN: + ev - ev to check + """ + if self.most_seen_ev is None: + self.most_seen_ev = ev + elif self.most_seen_ev.shown_count < ev.shown_count: + self.most_seen_ev = ev + + + def inDB(self, ev): + """ + returns true if the given ev is in this db + """ + return ev.eventlabel in self.db + + + def stat(self, ev): + """ + adds the given ev to the stats table. + Doesn't check if this ev belongs to this db + + Returns the value from _seen + """ + self.checkAndReplaceMostSeen(ev) + self.show_count += ev.shown_count + self.ev_count += 1 + + if ev.pool: + self.pool_count += 1 + self.pshow_count += ev.shown_count + + if ev.random: + self.rand_count += 1 + self.rshow_count += ev.shown_count + + if ev.unlocked: + self.unlo_count += 1 + else: + self.lock_count += 1 + + _seen = renpy.seen_label(ev.eventlabel) + if _seen: + self.seen_count += 1 + + return _seen + + + def __str__(self): + """ + to String + """ + sc_avg, psc_avg, rsc_avg = self.calcAvgs() + return ( + self._ev_finalstatline.format( + self.ev_count, + self.pool_count, + self.rand_count, + self.unlo_count, + self.lock_count, + self.seen_count, + self.show_count, + sc_avg, + self.pshow_count, + psc_avg, + self.rshow_count, + rsc_avg, + self.name + ) + "\n[MOST]: " + + self._ev_statline.format( + self.most_seen_ev.eventlabel, + self.most_seen_ev.pool, + self.most_seen_ev.random, + self.most_seen_ev.unlocked, + "N/A", + self.most_seen_ev.shown_count + ) + ) + + + @staticmethod + def getSortKey(_statcounter): + """ + Sort key for a statcounter is the number of entries in its + database + """ + return len(_statcounter.db) + + # we have 5 databaess to keep track of + statcounters = [ + StatCounter("events", event_database), + StatCounter("byes", farewell_database), + StatCounter("greetings", greeting_database), + StatCounter("moods", mood_db), + StatCounter("stories", story_database) + ] + statcounters.sort(key=StatCounter.getSortKey, reverse=True) + def _stat(_scs, ev): + """ + Short-circuting stat counter + + returns seen value + """ + for _sc in _scs: + if _sc.inDB(ev): + return _sc.stat(ev) + + return "N/A" + + with open(_ev_stats_fp, "w") as _ev_stats_file: + + _ev_stats_file.write(config.version + "\n\n") + _ev_stats_file.write(mas_sessionDataDump()) + + # gather data + for ev in mas_all_ev_db.values(): + _seen = _stat(statcounters, ev) + + # print event stats + _ev_stats_file.write(StatCounter._ev_statline.format( + ev.eventlabel, + ev.pool, + ev.random, + ev.unlocked, + _seen, + ev.shown_count + )) + + # print final stats (including most seen) + for _sc in statcounters: + _ev_stats_file.write(str(_sc)) + + + def mas_unstableDataDump(): + """ + This is a function called on startup and performs data dumps. + + Please add your data dump to a different file than dumps.log if its + a large dump. + + Thank you. + """ + mas_eventDataDump() + mas_varDataDump() + + + def mas_sessionDataDump(): + """ + Dumps session data as a string + """ + if persistent.sessions is None: + return "No session data found." + + # grab each data element + first_sesh = persistent.sessions.get("first_session", "N/A") + total_sesh = persistent.sessions.get("total_sessions", None) + curr_sesh_st = persistent.sessions.get("current_session_start", "N/A") + total_playtime = persistent.sessions.get("total_playtime", None) + last_sesh_ed = persistent.sessions.get("last_session_end", "N/A") + + if total_sesh is not None and total_playtime is not None: + avg_sesh = total_playtime / total_sesh + + else: + avg_sesh = "N/A" + + # which ones do we actually have + def cts(sesh): + if sesh is None: + return "N/A" + + return sesh + + + # assemble output + output = [ + first_sesh, + cts(total_sesh), + cts(total_playtime), + avg_sesh, + curr_sesh_st, + last_sesh_ed + ] + + # NOTE: curr_sesh_st -> last session start because it gets updated + # during ch30 + outstr = ( + "First session: {0}\n" + + "Total sessions: {1}\n" + + "Total playtime: {2}\n" + + "Avg playtime per session: {3}\n" + + "Last session start: {4}\n" + + "Last session end: {5}\n\n" + ) + + return outstr.format(*output) + + + def mas_varDataDump(): + """ + Dumps other kinds of data. + """ + import os + + # setup filepath + _var_data = "/var_dump.log" + _var_data_fp = os.path.normcase(renpy.config.basedir + _var_data) + + with open(_var_data_fp, "w") as _var_data_file: + _var_data_file.write(config.version + "\n\n") + + # add data lines here + _var_data_file.write("CUPS OF COFFEE DRANK: {0}".format( + persistent._mas_coffee_cups_drank + )) + + + if persistent._mas_unstable_mode or mas_r7_mode: + mas_unstableDataDump() + From c93327a47173615ffc58d14ebe6eba25604cd6fd Mon Sep 17 00:00:00 2001 From: potato Date: Wed, 6 Nov 2019 23:08:01 -0600 Subject: [PATCH 002/180] fixed some issues --- Monika After Story/game/definitions.rpy | 5 ++++- Monika After Story/game/updater.rpy | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 870ed7013c..ca23bde52e 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -19,6 +19,9 @@ python early: import traceback _dev_tb_list = [] + # r7 mode + mas_r7_mode = True + # uncomment this if you want syntax highlighting support on vim # init -1 python: @@ -3409,7 +3412,7 @@ init -990 python in mas_utils: # unstable should never delete logs - if store.persistent._mas_unstable_mode or mas_r7_mode: + if store.persistent._mas_unstable_mode or store.mas_r7_mode: mas_log = getMASLog("log/mas_log", append=True, flush=True) else: mas_log = getMASLog("log/mas_log") diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index caa72468fc..647add315e 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -1,5 +1,3 @@ -# r7 mode -define mas_r7_mode = True # enabling unstable mode default persistent._mas_unstable_mode = False @@ -739,7 +737,7 @@ init -1 python: init python in mas_updater: - + import store def checkUpdate(): """ @@ -753,7 +751,7 @@ init python in mas_updater: curr_time = time.time() - if mas_r7_mode: + if store.mas_r7_mode: update_link = r7 elif renpy.game.persistent._mas_unstable_mode: From 07d701e4f412b7069bb997b170cb8fd5ff901efd Mon Sep 17 00:00:00 2001 From: potato Date: Wed, 6 Nov 2019 23:12:11 -0600 Subject: [PATCH 003/180] build lib and renpy as well and add readme --- Monika After Story/README.html | 195 ---------------------------- Monika After Story/README.md | 4 + Monika After Story/game/options.rpy | 3 + 3 files changed, 7 insertions(+), 195 deletions(-) delete mode 100644 Monika After Story/README.html create mode 100644 Monika After Story/README.md diff --git a/Monika After Story/README.html b/Monika After Story/README.html deleted file mode 100644 index 3399f24087..0000000000 --- a/Monika After Story/README.html +++ /dev/null @@ -1,195 +0,0 @@ - - -Doki Doki Literature Club! Readme - - - -
- -
-

Monika After Story

-

Disclaimer

-

- Monika After Story is a Doki Doki Literature Club fan game that is not affiliated with Team Salvato. It is designed to be played only after the official game has been completed. You can download Doki Doki Literature Club at: http://ddlc.moe -

-

Installation

-

- The files in Monika After Story alter Doki Doki Literature Club. You must have an installation of DDLC, which can be downloaded for free from http://ddlc.moe. -

-

- To install, extract the files from the Monika After Story zip archive to the DDLC /game folder. Opening Doki Doki Literature Club will now load with all of the modified files. -

-

- If you are running DDLC on Steam, you can access the game files by right-clicking the game in your Library, then selecting Properties. In the Local Files tab, click the Browse local files... button. This will open a window with the game directory. -

-

- Mac users can access the game directory by right-clicking on the DDLC application and selecting Show Package Contents. Then, the game directory can be found in Contents/Resources/autorun. -

-

- To uninstall this mod, simply delete all added files in the game directory. -

-

Basic Help

-

- To advance through the game, left-click or press the space or enter keys. When at a menu, - left-click to make a choice, or use the arrow keys to select a choice and enter to activate it. -

- -

Game Menu

-

- When playing a game, press the escape key to enter the game menu. The game menu - gives the following choices: -

- -
-
Return
-
Returns to the game.
-
Save Game
-
Allows you to save a game by clicking on a save slot.
-
Load Game
-
Allows you to load a game by clicking on a save slot. Clicking on "Auto" accesses the automatic save slots.
-
Preferences
-
- Changes the game preferences (options/configuration): -
-
Display
-
Switches between fullscreen and windowed mode.
-
Text Speed
-
Controls the rate at which text displays. The further to the right this slider is, the faster the text - will display. All the way to the right causes text to be shown instantly.
-
Skip - Unseen Text
-
Chooses between skipping messages that have been already seen (in any play through the game), and - skipping all messages.
-
Skip - After Choices
-
Controls if skipping stops upon reaching a menu.
-
Auto-Forward Time
-
Controls automatic advance. The further to the left this slider is, the shorter the amount of time - before the game advances. All the way to the right means text will never auto-forward.
-
Music and Sound Volume
-
Controls the volume of the Music and Sound effect channels, respectively. The further to the - right these are, the louder the volume.
-
-
-
Main Menu
-
Returns to the main menu, ending the current game.
-
Quit
-
Exits the game; the game will be closed and ended.
-
- -

Key and Mouse Bindings

-
-
Left-click, Enter
-
Advances through the game, activates menu choices, buttons, and sliders.
-
Space
-
Advances through the game, but does not activate choices.
-
Arrow Keys
-
Navigates between menu choices, buttons, and sliders.
-
Ctrl
-
Causes skipping to occur while the ctrl key is held down.
-
Escape
-
Enters the game menu. When in the game menu, returns to the game.
-
Right-click, H
-
Hides the text window and other transient displays.
-
F
-
Toggles fullscreen mode
-
S
-
Takes a screenshot, saving it in a file named screenshotxxxx.png, where xxxx is a serial number.
-
Alt-M, Command-H
-
Hides (iconifies) the window.
-
Alt-F4, Command-Q
-
Quits the game.
-
Delete
-
When a save slot is selected, deletes that save slot.
-
M
-
Opens the Music Menu, when available
-
T
-
Opens Talk dialogue box, when available
-
P
-
Opens Play game menu, when available
-
Shift+M
-
Mutes the music
-
- (minus)
-
Decreases music volume
-
+ (plus)
-
Increases music volume
-
- -

Loading and Deleting Save Data

-

- Some mods may offer the option to load some or all of your data from a previous Doki Doki Literature Club playthrough. If so, that option will be presented on the first load of the game, after the initial content warning and disclaimer. -

-

- If you do choose to load previous data, the original Doki Doki Literature Club save data will not be changed. -

-

- In some cases, you may want to delete your save data to start the game completely over. To do this, delete the persistent file for this mod. Follow this link to learn where this data is stored on your system. -

-

- Deleting save data for a mod will not alter or delete original save data from Doki Doki Literature Club. -

-

Legal Notice

-

- This program contains free software licensed under a number of licenses, including the GNU Lesser Public License. A - complete list of software is available at http://www.renpy.org/doc/html/license.html. -

-

- This mod is a fan work. Doki Doki Literature Club and all related characters and assets are the property of Team Salvato. Intellectual property in this mod is used in accordance with Team Salvato's Intellectual Property Guidelines, which can be found at http://teamsalvato.com/ip-guidelines/. -

-
- -
- - - diff --git a/Monika After Story/README.md b/Monika After Story/README.md new file mode 100644 index 0000000000..b1663ce614 --- /dev/null +++ b/Monika After Story/README.md @@ -0,0 +1,4 @@ +# Install instructions +1. copy all files here to your root DDLC dir. (`ddlc-win/`) + **NOTE: This is NOT the same as regular MAS installs.** +2. Overwrite all files. diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index ec4c14d8a5..4b45631144 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -236,6 +236,9 @@ init python: build.classify("game/python-packages/**",build.name)#Additional python pacakges build.classify("CustomIcon**.**",build.name) + # add lib and renpy + build.classify("lib/*", build.name) + build.classify("renpy/*", build.name) build.package(build.directory_name + "Mod",'zip',build.name,description='DDLC Compatible Mod') From 155a9715ed44eef90ffc84ca39af0d4f57e108ff Mon Sep 17 00:00:00 2001 From: potato Date: Wed, 6 Nov 2019 23:17:58 -0600 Subject: [PATCH 004/180] actually hjtml read mebettrer --- Monika After Story/README.html | 193 +++++++++++++++++++++++++++++++++ Monika After Story/README.md | 4 - 2 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 Monika After Story/README.html delete mode 100644 Monika After Story/README.md diff --git a/Monika After Story/README.html b/Monika After Story/README.html new file mode 100644 index 0000000000..0c9dd38ae1 --- /dev/null +++ b/Monika After Story/README.html @@ -0,0 +1,193 @@ + + +Doki Doki Literature Club! Readme + + + +
+ +
+

Monika After Story

+

Disclaimer

+

+ Monika After Story is a Doki Doki Literature Club fan game that is not affiliated with Team Salvato. It is designed to be played only after the official game has been completed. You can download Doki Doki Literature Club at: http://ddlc.moe +

+

Installation

+

Installation of an r7 build is NOT REVERSABLE

+

+ The files in Monika After Story alter Doki Doki Literature Club. You must have an installation of DDLC, which can be downloaded for free from http://ddlc.moe. +

+

+ To install, extract the files from the Monika After Story zip archive to the DDLC folder. This is different than the usual install method for regular MAS installs. Opening Doki Doki Literature Club will now load with all of the modified files. +

+

+ If you are running DDLC on Steam, you can access the game files by right-clicking the game in your Library, then selecting Properties. In the Local Files tab, click the Browse local files... button. This will open a window with the game directory. +

+

+ Mac users can access the game directory by right-clicking on the DDLC application and selecting Show Package Contents. Then, the game directory can be found in Contents/Resources/autorun. +

+

Basic Help

+

+ To advance through the game, left-click or press the space or enter keys. When at a menu, + left-click to make a choice, or use the arrow keys to select a choice and enter to activate it. +

+ +

Game Menu

+

+ When playing a game, press the escape key to enter the game menu. The game menu + gives the following choices: +

+ +
+
Return
+
Returns to the game.
+
Save Game
+
Allows you to save a game by clicking on a save slot.
+
Load Game
+
Allows you to load a game by clicking on a save slot. Clicking on "Auto" accesses the automatic save slots.
+
Preferences
+
+ Changes the game preferences (options/configuration): +
+
Display
+
Switches between fullscreen and windowed mode.
+
Text Speed
+
Controls the rate at which text displays. The further to the right this slider is, the faster the text + will display. All the way to the right causes text to be shown instantly.
+
Skip - Unseen Text
+
Chooses between skipping messages that have been already seen (in any play through the game), and + skipping all messages.
+
Skip - After Choices
+
Controls if skipping stops upon reaching a menu.
+
Auto-Forward Time
+
Controls automatic advance. The further to the left this slider is, the shorter the amount of time + before the game advances. All the way to the right means text will never auto-forward.
+
Music and Sound Volume
+
Controls the volume of the Music and Sound effect channels, respectively. The further to the + right these are, the louder the volume.
+
+
+
Main Menu
+
Returns to the main menu, ending the current game.
+
Quit
+
Exits the game; the game will be closed and ended.
+
+ +

Key and Mouse Bindings

+
+
Left-click, Enter
+
Advances through the game, activates menu choices, buttons, and sliders.
+
Space
+
Advances through the game, but does not activate choices.
+
Arrow Keys
+
Navigates between menu choices, buttons, and sliders.
+
Ctrl
+
Causes skipping to occur while the ctrl key is held down.
+
Escape
+
Enters the game menu. When in the game menu, returns to the game.
+
Right-click, H
+
Hides the text window and other transient displays.
+
F
+
Toggles fullscreen mode
+
S
+
Takes a screenshot, saving it in a file named screenshotxxxx.png, where xxxx is a serial number.
+
Alt-M, Command-H
+
Hides (iconifies) the window.
+
Alt-F4, Command-Q
+
Quits the game.
+
Delete
+
When a save slot is selected, deletes that save slot.
+
M
+
Opens the Music Menu, when available
+
T
+
Opens Talk dialogue box, when available
+
P
+
Opens Play game menu, when available
+
Shift+M
+
Mutes the music
+
- (minus)
+
Decreases music volume
+
+ (plus)
+
Increases music volume
+
+ +

Loading and Deleting Save Data

+

+ Some mods may offer the option to load some or all of your data from a previous Doki Doki Literature Club playthrough. If so, that option will be presented on the first load of the game, after the initial content warning and disclaimer. +

+

+ If you do choose to load previous data, the original Doki Doki Literature Club save data will not be changed. +

+

+ In some cases, you may want to delete your save data to start the game completely over. To do this, delete the persistent file for this mod. Follow this link to learn where this data is stored on your system. +

+

+ Deleting save data for a mod will not alter or delete original save data from Doki Doki Literature Club. +

+

Legal Notice

+

+ This program contains free software licensed under a number of licenses, including the GNU Lesser Public License. A + complete list of software is available at http://www.renpy.org/doc/html/license.html. +

+

+ This mod is a fan work. Doki Doki Literature Club and all related characters and assets are the property of Team Salvato. Intellectual property in this mod is used in accordance with Team Salvato's Intellectual Property Guidelines, which can be found at http://teamsalvato.com/ip-guidelines/. +

+
+ +
+ + + diff --git a/Monika After Story/README.md b/Monika After Story/README.md deleted file mode 100644 index b1663ce614..0000000000 --- a/Monika After Story/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Install instructions -1. copy all files here to your root DDLC dir. (`ddlc-win/`) - **NOTE: This is NOT the same as regular MAS installs.** -2. Overwrite all files. From 154105c8bded01b90680357cc1d819f6409dd613 Mon Sep 17 00:00:00 2001 From: potato Date: Wed, 6 Nov 2019 23:22:58 -0600 Subject: [PATCH 005/180] everything --- Monika After Story/game/options.rpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 4b45631144..f58cdcc49f 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -237,8 +237,8 @@ init python: build.classify("CustomIcon**.**",build.name) # add lib and renpy - build.classify("lib/*", build.name) - build.classify("renpy/*", build.name) + build.classify("lib/**", build.name) + build.classify("renpy/**", build.name) build.package(build.directory_name + "Mod",'zip',build.name,description='DDLC Compatible Mod') From 4ab5e40da94d368528a356f01005ea10c9f0625f Mon Sep 17 00:00:00 2001 From: multimokia Date: Fri, 8 Nov 2019 14:22:33 -0500 Subject: [PATCH 006/180] fix weath looping too early --- Monika After Story/game/script-ch30.rpy | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 0cbf7cdbcf..67bb2b5798 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -61,6 +61,8 @@ init -1 python in mas_globals: last_day = None # numbr of the day we last ran ch30_day + previous_weath = None + init 970 python: import store.mas_filereacts as mas_filereacts @@ -372,7 +374,10 @@ init python: mask += "_fb" # now show the mask - renpy.show(mask, tag="rm") + if store.mas_globals.previous_weath != mask: + renpy.show(mask) + store.mas_globals.previous_weath = mask + if dissolve_masks: renpy.with_statement(Dissolve(1.0)) From af368a77ac452515388032a3fe9c2b174ee4c036 Mon Sep 17 00:00:00 2001 From: multimokia Date: Fri, 8 Nov 2019 15:42:41 -0500 Subject: [PATCH 007/180] fix first sesh for dump file --- Monika After Story/game/zz_dump.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy index a9abdff8af..cf27d9a0bb 100644 --- a/Monika After Story/game/zz_dump.rpy +++ b/Monika After Story/game/zz_dump.rpy @@ -255,7 +255,7 @@ init 999 python: total_playtime = persistent.sessions.get("total_playtime", None) last_sesh_ed = persistent.sessions.get("last_session_end", "N/A") - if total_sesh is not None and total_playtime is not None: + if total_sesh and total_playtime is not None: avg_sesh = total_playtime / total_sesh else: From c449879cf20e23e69e40a753c61786591d3e75e7 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 10 Nov 2019 00:09:14 -0500 Subject: [PATCH 008/180] actually show using the tag and remove when needed --- Monika After Story/game/script-ch30.rpy | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 67bb2b5798..3522df7bdf 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -363,9 +363,6 @@ init python: mas_is_raining mas_is_snowing """ - # hide the existing mask - renpy.hide("rm") - # get current weather masks mask = mas_current_weather.sp_window(morning_flag) @@ -375,7 +372,9 @@ init python: # now show the mask if store.mas_globals.previous_weath != mask: - renpy.show(mask) + # hide the existing mask + renpy.hide("rm") + renpy.show(mask, tag="rm") store.mas_globals.previous_weath = mask From 0fc876e1e82d781ae26ff5624d4e9326c12f189c Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 5 Jan 2020 23:05:07 -0500 Subject: [PATCH 009/180] you make me angery --- Monika After Story/game/definitions.rpy | 5 +++++ Monika After Story/game/script-ch30.rpy | 14 ++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index ca23bde52e..61323f0628 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -1,6 +1,11 @@ define persistent.demo = False define persistent.steam = False define config.developer = False #This is the flag for Developer tools +define config.replay_movie_sprites = False #Fix masks +#Fix menu textbox issues +define config.menu_showed_window = True +define config.window_auto_show = ["say"] +define config.window_auto_hide = ["scene", "call screen"] init 1 python: persistent.steam = "steamapps" in config.basedir.lower() diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 3522df7bdf..c86e4c0887 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -61,8 +61,6 @@ init -1 python in mas_globals: last_day = None # numbr of the day we last ran ch30_day - previous_weath = None - init 970 python: import store.mas_filereacts as mas_filereacts @@ -353,16 +351,17 @@ init python: """ Draws the appropriate masks according to the current state of the game. - IN: dissolve_masks - True will dissolve masks, False will not (Default; True) - ASSUMES: morning_flag mas_is_raining mas_is_snowing """ + # hide the existing mask + renpy.hide("rm") + # get current weather masks mask = mas_current_weather.sp_window(morning_flag) @@ -371,12 +370,7 @@ init python: mask += "_fb" # now show the mask - if store.mas_globals.previous_weath != mask: - # hide the existing mask - renpy.hide("rm") - renpy.show(mask, tag="rm") - store.mas_globals.previous_weath = mask - + renpy.show(mask, tag="rm") if dissolve_masks: renpy.with_statement(Dissolve(1.0)) From f5554f9555ddcfa10a160c777ee2a8a19071ba89 Mon Sep 17 00:00:00 2001 From: potato Date: Sun, 12 Jan 2020 18:49:37 -0600 Subject: [PATCH 010/180] r7 update --- Monika After Story/game/options.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index f58cdcc49f..13cc276dee 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -23,7 +23,7 @@ define gui.show_name = False ## The version of the game. -define config.version = "0.10.3-r7-2019.11.06.1" +define config.version = "0.10.5-r7-2020.01.12.002" ## Text that is placed on the game's about screen. To insert a blank line ## between paragraphs, write \n\n. From 8c2ce71d9758d51e7615139b55c38141759fe161 Mon Sep 17 00:00:00 2001 From: potato Date: Sun, 12 Jan 2020 18:51:49 -0600 Subject: [PATCH 011/180] updated travis for r7 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index bc3383f704..506604ffaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,10 @@ install: - cd .. # renpy - - wget https://www.renpy.org/dl/6.99.12.4/renpy-6.99.12.4-sdk.tar.bz2 - - tar xf renpy-6.99.12.4-sdk.tar.bz2 - - rm renpy-6.99.12.4-sdk.tar.bz2 - - mv renpy-6.99.12.4-sdk renpy + - wget https://www.renpy.org/dl/7.1.1/renpy-7.1.1-sdk.tar.bz2 + - tar xf renpy-7.1.1-sdk.tar.bz2 + - rm renpy-7.1.1-sdk.tar.bz2 + - mv renpy-7.1.1-sdk renpy # mas build - wget https://s3-us-west-2.amazonaws.com/monika-after-story/ddlc/mas.zip From 3327b2c590071b0d34d5cb78a3ffc79acadd8ef7 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 30 Apr 2020 23:45:41 -0400 Subject: [PATCH 012/180] fix broken submods screen --- Monika After Story/game/screens.rpy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 58aa2d3eba..dd10580384 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -2734,7 +2734,7 @@ style chibika_note_text: screen submods(): tag menu - use game_menu(("Submods"), scroll="viewport"): + use game_menu(("Submods"), scroll=None): default tooltip = Tooltip("") @@ -2767,8 +2767,6 @@ screen submods(): text submod.description $ renpy.display.screen.use_screen(submod.settings_pane, _name="{0}_{1}".format(submod.author, submod.name)) - vbar value YScrollValue("scrollme") - text tooltip.value: xalign 0 yalign 1.0 xoffset 300 yoffset -10 From 0e5aa9d42a62abd18cd4e9d38733aef4f50b93ea Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 30 Apr 2020 23:50:05 -0400 Subject: [PATCH 013/180] dang invisible conflicts --- Monika After Story/game/zz_dump.rpy | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy index 700affbd14..8623636ad2 100644 --- a/Monika After Story/game/zz_dump.rpy +++ b/Monika After Story/game/zz_dump.rpy @@ -77,11 +77,6 @@ init 999 python: def calcAvgs(self): """ Calculates averages -<<<<<<< HEAD - -======= - ->>>>>>> upstream/content Returns tuple: [0]: show count avg [1]: pool show count avg @@ -106,11 +101,6 @@ init 999 python: elif self.most_seen_ev.shown_count < ev.shown_count: self.most_seen_ev = ev -<<<<<<< HEAD - -======= - ->>>>>>> upstream/content def inDB(self, ev): """ returns true if the given ev is in this db @@ -148,11 +138,6 @@ init 999 python: return _seen -<<<<<<< HEAD - -======= - ->>>>>>> upstream/content def __str__(self): """ to String From 3530ac4ff238b15338d00b062fea452c8ed6a742 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 26 Dec 2020 20:12:00 -0500 Subject: [PATCH 014/180] dl + build renpy 7.4 instead --- .github/workflows/mas_check.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 2bf3cb942f..af1858e545 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -2,7 +2,7 @@ name: CI -# Controls when the action will run. +# Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the master branch push: @@ -18,10 +18,10 @@ jobs: # This workflow contains a single job called "build" build: name: ci_build - + # The type of runner that the job will run on runs-on: ubuntu-latest - + env: SDL_AUDIODRIVER: dummy # handles ALSA issues @@ -39,11 +39,11 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget https://www.renpy.org/dl/6.99.12.4/renpy-6.99.12.4-sdk.tar.bz2 - tar xf renpy-6.99.12.4-sdk.tar.bz2 - rm renpy-6.99.12.4-sdk.tar.bz2 - mv renpy-6.99.12.4-sdk renpy - + wget https://www.renpy.org/dl/7.4.0/renpy-7.4.0-sdk.tar.bz2 + tar xf renpy-7.4.0-sdk.tar.bz2 + rm renpy-7.4.0-sdk.tar.bz2 + mv renpy-7.4.0-sdk renpy + # download base mas - name: Download base MAS + copy files over run: | @@ -56,7 +56,7 @@ jobs: - name: exception skip for unstable run: touch mas0105/trb if: github.ref == 'refs/heads/unstable' - + # run sprite checkers - name: check sprites run: python tools/ghactions.py @@ -74,4 +74,3 @@ jobs: run: | cd renpy ./renpy.sh launcher distribute "../mas0105/" --package Mod - From d2e564e1574de369b785412a0a14f9da13751d97 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 27 Dec 2020 14:57:23 -0500 Subject: [PATCH 015/180] fix exception on exit --- Monika After Story/game/definitions.rpy | 16 +++++++++++++--- Monika After Story/game/zz_calendar.rpy | 5 +++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 1ead88dbc2..93ee14ca30 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -3,15 +3,25 @@ define persistent.demo = False define config.developer = False #This is the flag for Developer tools # define persistent.steam = "steamapps" in config.basedir.lower() -##R7+ Config Var adjustments -#Fixes spaceroom masks from restarting every interaction -define config.replay_movie_sprites = False +###R7+ Config Var adjustments +##7.3.3 +#Only devs need this +define config.report_extraneous_attributes = False +##7.3.0 +define config.keyword_after_python = True +##7.1.1 #Fix menu textbox issues define config.menu_showed_window = True #Fix textbox sometimes disappearing define config.window_auto_show = ["say"] #Fix textbox flickering define config.window_auto_hide = ["scene", "call screen"] +##7.0 +#Fixes spaceroom masks from restarting every interaction +define config.replay_movie_sprites = False +##6.99.13 +define config.atl_one_frame = False + python early: import singleton diff --git a/Monika After Story/game/zz_calendar.rpy b/Monika After Story/game/zz_calendar.rpy index a5165779cc..58bba630d2 100644 --- a/Monika After Story/game/zz_calendar.rpy +++ b/Monika After Story/game/zz_calendar.rpy @@ -1279,8 +1279,9 @@ init -1 python in mas_calendar: the database - database a dict containing the events """ - with open(renpy.config.savedir + '/db.mcal', 'w') as fp: - json.dump(calendar_database, fp, cls=encoder) + #TODO: Remove the unicode conversion once r8 is here + with open(renpy.config.savedir + '/db.mcal', 'w', encoding="utf-8") as fp: + fp.write(unicode(json.dumps(calendar_database, cls=encoder, ensure_ascii=False))) def loadCalendarDatabase(): From 79f3c7826db963e3e90823be62c3a5825562a0c8 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 27 Dec 2020 21:16:06 -0500 Subject: [PATCH 016/180] I really hate that this is the solution, but it is fix for .keys() --- Monika After Story/game/chess.rpy | 1 + Monika After Story/game/definitions.rpy | 1 + Monika After Story/game/event-handler.rpy | 11 ++++--- Monika After Story/game/event-rules.rpy | 3 +- Monika After Story/game/import_ddlc.rpy | 1 + Monika After Story/game/script-affection.rpy | 3 +- Monika After Story/game/script-apologies.rpy | 2 +- Monika After Story/game/script-ch30.rpy | 1 + Monika After Story/game/script-fun-facts.rpy | 2 +- Monika After Story/game/script-stories.rpy | 2 +- Monika After Story/game/script-topics.rpy | 1 + .../game/sprite-chart-matrix.rpy | 7 ++-- Monika After Story/game/sprite-chart.rpy | 13 ++++---- Monika After Story/game/updater.rpy | 2 +- Monika After Story/game/updates.rpy | 7 ++-- Monika After Story/game/updates_topics.rpy | 1 + Monika After Story/game/zz_backgrounds.rpy | 33 ++++++++++--------- Monika After Story/game/zz_backup.rpy | 5 +-- Monika After Story/game/zz_calendar.rpy | 1 + Monika After Story/game/zz_dockingstation.rpy | 5 +-- Monika After Story/game/zz_reactions.rpy | 7 ++-- Monika After Story/game/zz_selector.rpy | 7 ++-- Monika After Story/game/zz_spritedeco.rpy | 2 +- Monika After Story/game/zz_spritejsons.rpy | 5 +-- Monika After Story/game/zz_windowreacts.rpy | 3 +- 25 files changed, 73 insertions(+), 53 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index de5d1d47f1..8d2cfd7cf0 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -1,3 +1,4 @@ +rpy python 3 #TODO: Fix label names. It's difficult to follow # we now will keep track of player wins / losses/ draws/ whatever diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 93ee14ca30..b5a6eebb91 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -1,3 +1,4 @@ +rpy python 3 define persistent.demo = False define config.developer = False #This is the flag for Developer tools diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index b5980811b2..3546c48eb7 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that defines functions for story event handling # Assumes: # persistent.event_list @@ -255,7 +256,7 @@ init -950 python in mas_ev_data_ver: if per_db is None: return - for ev_label in per_db.keys(): + for ev_label in tuple(per_db.keys()): # pull out the data ev_line = per_db[ev_label] @@ -392,7 +393,7 @@ init 6 python: """ Context manager wrapper for Event objects via event labels. This has handling for when an eventlabel doesn't return an actual - event object via mas_getEV. + event object via mas_getEV. Use as follows: with MASev('some event label') as ev: @@ -401,7 +402,7 @@ init 6 python: property names should be same as used on Event object. functions can also be used. - additionally, the resulting context object can be compared with + additionally, the resulting context object can be compared with other event objects like normal. In cases where the Event does not exist, the following occurs: @@ -457,7 +458,7 @@ init 6 python: def __getattr__(self, name): if self._ev is None: - + # event props if name in MAS_EVL._default_values: return MAS_EVL._default_values.get(name) @@ -1645,7 +1646,7 @@ init -1 python in evhand: Goes through the year setblacklist and removes expired entries """ now_dt = datetime.datetime.now() - for evl in store.persistent._mas_ev_yearset_blacklist.keys(): + for evl in tuple(store.persistent._mas_ev_yearset_blacklist.keys()): if store.persistent._mas_ev_yearset_blacklist[evl] <= now_dt: store.persistent._mas_ev_yearset_blacklist.pop(evl) diff --git a/Monika After Story/game/event-rules.rpy b/Monika After Story/game/event-rules.rpy index a1b058f789..ce0a8e9557 100644 --- a/Monika After Story/game/event-rules.rpy +++ b/Monika After Story/game/event-rules.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that defines static classes used to create the rule tuples used in the # Event class. # The static classes are the ones used to manipulate the rule tuples @@ -937,7 +938,7 @@ init python: NOTE: uses mas_getEV """ - for ev_label in persistent._mas_undo_action_rules.keys(): + for ev_label in tuple(persistent._mas_undo_action_rules.keys()): ev = mas_getEV(ev_label) #Since we can have differing returns, we store this to use later should_undo = MASUndoActionRule.evaluate_rule(ev) diff --git a/Monika After Story/game/import_ddlc.rpy b/Monika After Story/game/import_ddlc.rpy index 3198eedce0..66dd2d84a5 100644 --- a/Monika After Story/game/import_ddlc.rpy +++ b/Monika After Story/game/import_ddlc.rpy @@ -1,3 +1,4 @@ +rpy python 3 # This file imports save data from DDLC without changing the original data. # By default, when this is run all relevant data from DDLC is imported. # Handling of individual variables can be handled by changing the settings below. diff --git a/Monika After Story/game/script-affection.rpy b/Monika After Story/game/script-affection.rpy index 86740dbacf..d755f9cb92 100644 --- a/Monika After Story/game/script-affection.rpy +++ b/Monika After Story/game/script-affection.rpy @@ -1,3 +1,4 @@ +rpy python 3 # AFF010 is progpoints # # Affection module: @@ -2215,7 +2216,7 @@ label mas_player_nickname_loop(check_scrollable_text, nickname_pool): python: done = False - acceptable_nicknames = _return.keys() + acceptable_nicknames = list(_return.keys()) if acceptable_nicknames: dlg_line = "Is there anything else you'd like me to call you?" diff --git a/Monika After Story/game/script-apologies.rpy b/Monika After Story/game/script-apologies.rpy index bb5cc0e9c4..c53e9c263d 100644 --- a/Monika After Story/game/script-apologies.rpy +++ b/Monika After Story/game/script-apologies.rpy @@ -26,7 +26,7 @@ init python: _today = datetime.date.today() #Iter thru the stuffs in the apology time tb - for ev_label in persistent._mas_apology_time_db.keys(): + for ev_label in tuple(persistent._mas_apology_time_db.keys()): if current_total_playtime >= persistent._mas_apology_time_db[ev_label][0] or _today >= persistent._mas_apology_time_db[ev_label][1]: #Pop the ev_label from the time db and lock the event label. You just lost your chance store.mas_lockEVL(ev_label,'APL') diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 7b357f4cc9..15a3c721c3 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -1,3 +1,4 @@ +rpy python 3 default persistent.monika_reload = 0 default persistent.tried_skip = False default persistent.monika_kill = True #Assume non-merging players killed monika. diff --git a/Monika After Story/game/script-fun-facts.rpy b/Monika After Story/game/script-fun-facts.rpy index fdc7609974..35bd34c577 100644 --- a/Monika After Story/game/script-fun-facts.rpy +++ b/Monika After Story/game/script-fun-facts.rpy @@ -25,7 +25,7 @@ init -10 python in mas_fun_facts: OUT: List of all fun fact eventlabels """ - return fun_fact_db.keys() + return list(fun_fact_db.keys()) #Whether or not the last fun fact seen was a good fact diff --git a/Monika After Story/game/script-stories.rpy b/Monika After Story/game/script-stories.rpy index 03480e657e..902daa24e3 100644 --- a/Monika After Story/game/script-stories.rpy +++ b/Monika After Story/game/script-stories.rpy @@ -312,7 +312,7 @@ label mas_story_unlock_random_cat(scary=False): ) # select one story randomly - story = stories[renpy.random.choice(stories.keys())] + story = stories[renpy.random.choice(tuple(stories.keys()))] # unlock the story story.unlocked = True diff --git a/Monika After Story/game/script-topics.rpy b/Monika After Story/game/script-topics.rpy index 252eab3aa7..bc2262a7b1 100644 --- a/Monika After Story/game/script-topics.rpy +++ b/Monika After Story/game/script-topics.rpy @@ -1,3 +1,4 @@ +rpy python 3 #This file contains all of monika's topics she can talk about #Each entry should start with a database entry, including the appropriate flags #to either be a random topic, a prompt "pool" topics, or a special conditional diff --git a/Monika After Story/game/sprite-chart-matrix.rpy b/Monika After Story/game/sprite-chart-matrix.rpy index 97157672cb..3cc336c0f0 100644 --- a/Monika After Story/game/sprite-chart-matrix.rpy +++ b/Monika After Story/game/sprite-chart-matrix.rpy @@ -1,3 +1,4 @@ +rpy python 3 # sprite generation using matrix for night sprites # TODO: look at adding a highlight option to ACS/Clothes/Hair @@ -1028,10 +1029,10 @@ init -4 python in mas_sprites: Clears all caches """ for cid, cache in CACHE_TABLE.iteritems(): - for key in cache.keys(): + for key in tuple(cache.keys()): cache.pop(key) - for key in MFM_CACHE.keys(): + for key in tuple(MFM_CACHE.keys()): MFM_CACHE.pop(key) @@ -2962,7 +2963,7 @@ init -10 python: RETURNS: list of all filter names in this map """ - return self.__mfm.map.keys() + return list(self.__mfm.map.keys()) def get(self, flt, defval=None): """ diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index 2feb344d66..8675ad4e3e 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Monika's sprites! # To add new images, use the sprite adder tool in MonikaModDev/tools/toolsmenu # @@ -3140,7 +3141,7 @@ init -3 python: IN: exprop - exprop to check for """ - for acs_name in self.acs_list_map.keys(): + for acs_name in tuple(self.acs_list_map.keys()): _acs = store.mas_sprites.ACS_MAP.get(acs_name, None) if _acs and _acs.hasprop(exprop): self.remove_acs_in(_acs, self.acs_list_map[acs_name]) @@ -3153,7 +3154,7 @@ init -3 python: IN: mux_types - list of acs_types to remove from acs """ - for acs_name in self.acs_list_map.keys(): + for acs_name in tuple(self.acs_list_map.keys()): _acs = store.mas_sprites.ACS_MAP.get(acs_name, None) if _acs and _acs.acs_type in mux_types: self.remove_acs_in(_acs, self.acs_list_map[acs_name]) @@ -3881,7 +3882,7 @@ init -3 python: vhl_data, msg_log, ind_lvl, - layer_map.keys() + list(layer_map.keys()) ): # success hl_data = vhl_data.get("hl_data", None) @@ -3952,7 +3953,7 @@ init -3 python: IN: mapping - mapping to clean """ - for map_key in mapping.keys(): + for map_key in tuple(mapping.keys()): if map_key not in self.__MPA_KEYS: mapping.pop(map_key) @@ -4155,7 +4156,7 @@ init -3 python: arm_data - cleaned arm data """ # first validate the arm data - for arm_key in arm_data.keys(): + for arm_key in tuple(arm_data.keys()): # then check if arm_key in store.mas_sprites.NUM_ARMS: @@ -5161,7 +5162,7 @@ init -3 python: if self.hl_map is None: return [] - return self.hl_map.keys() + return list(self.hl_map.keys()) def rmprop(self, prop): """ diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index e2e42112dd..a36a6775b5 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -1,4 +1,4 @@ - +rpy python 3 # enabling unstable mode default persistent._mas_unstable_mode = False default persistent._mas_can_update = True diff --git a/Monika After Story/game/updates.rpy b/Monika After Story/game/updates.rpy index aea6c1de26..d4f0ae6031 100644 --- a/Monika After Story/game/updates.rpy +++ b/Monika After Story/game/updates.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that handles updates between versions # Assumes: # updates.topics @@ -331,7 +332,7 @@ init 10 python: ] renpy.call_in_new_context("vv_updates_topics") - ver_list = store.updates.version_updates.keys() + ver_list = list(store.updates.version_updates.keys()) if "-" in config.version: working_version = config.version[:config.version.index("-")] @@ -1492,10 +1493,10 @@ label v0_10_3(version="v0_10_3"): python: #Convert fav/derand dicts to lists based on their keys if needed if isinstance(persistent._mas_player_bookmarked, dict): - persistent._mas_player_bookmarked = persistent._mas_player_bookmarked.keys() + persistent._mas_player_bookmarked = list(persistent._mas_player_bookmarked.keys()) if isinstance(persistent._mas_player_derandomed, dict): - persistent._mas_player_derandomed = persistent._mas_player_derandomed.keys() + persistent._mas_player_derandomed = list(persistent._mas_player_derandomed.keys()) return diff --git a/Monika After Story/game/updates_topics.rpy b/Monika After Story/game/updates_topics.rpy index 906279db92..d024b2a0f1 100644 --- a/Monika After Story/game/updates_topics.rpy +++ b/Monika After Story/game/updates_topics.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that defines changed topics between versions # this should run before updates.rpy diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index 41669a6d7a..cf2e021823 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Here's where we store our background data default persistent._mas_background_MBGdata = {} @@ -847,7 +848,7 @@ init -10 python: for sl_data in self._slices: filters[sl_data.flt_slice.name] = None - return filters.keys() + return list(filters.keys()) def first_flt(self): """ @@ -1231,11 +1232,11 @@ init -10 python: def __repr__(self): day_f = self._day_filters if day_f is not None: - day_f = day_f.keys() + day_f = list(day_f.keys()) night_f = self._night_filters if night_f is not None: - night_f = night_f.keys() + night_f = list(night_f.keys()) return ( " 0 and seen_images_key[0] == "monika" diff --git a/Monika After Story/game/zz_calendar.rpy b/Monika After Story/game/zz_calendar.rpy index 58bba630d2..17717131ef 100644 --- a/Monika After Story/game/zz_calendar.rpy +++ b/Monika After Story/game/zz_calendar.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Calendar module # A custom made Calendar like UI to help managing date based events # Contains also a store named mas_calendar which includes helper functions diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 9edd091955..f5129d2700 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that provides an interface for loading / saving files that we interact with # # NOTE: this is meant purely for reading / writing files into base64 with @@ -1175,7 +1176,7 @@ init -11 python in mas_dockstat: Returns TRUE upon success, False otherwise """ if len(selective) == 0: - selective = image_dict.keys() + selective = list(image_dict.keys()) for b64_name in selective: real_name, chksum = image_dict[b64_name] @@ -1262,7 +1263,7 @@ init -11 python in mas_dockstat: AKA quitting """ if len(selective) == 0: - selective = image_dict.keys() + selective = list(image_dict.keys()) for b64_name in selective: real_name, chksum = image_dict[b64_name] diff --git a/Monika After Story/game/zz_reactions.rpy b/Monika After Story/game/zz_reactions.rpy index f9bfef1da4..d383262a20 100644 --- a/Monika After Story/game/zz_reactions.rpy +++ b/Monika After Story/game/zz_reactions.rpy @@ -1,3 +1,4 @@ +rpy python 3 # FileReactions framework. # not too different from events @@ -557,7 +558,7 @@ init -11 python in mas_filereacts: # otherwise check for random deletion if _filename is None: - _filename = random.choice(_map.keys()) + _filename = random.choice(tuple(_map.keys())) file_to_delete = _map.get(_filename, None) if file_to_delete is None: @@ -669,7 +670,7 @@ init -11 python in mas_filereacts: IN: _map - map to delete all """ - _map_keys = _map.keys() + _map_keys = tuple(_map.keys()) for _key in _map_keys: _core_delete(_key, _map) @@ -832,7 +833,7 @@ init python: return (None, None, None, None, None) elif len(persistent._mas_filereacts_sprite_reacted) > 0: - sp_data = persistent._mas_filereacts_sprite_reacted.keys()[0] + sp_data = tuple(persistent._mas_filereacts_sprite_reacted.keys())[0] giftname = persistent._mas_filereacts_sprite_reacted[sp_data] else: diff --git a/Monika After Story/game/zz_selector.rpy b/Monika After Story/game/zz_selector.rpy index bff002bde3..1bcc5d185d 100644 --- a/Monika After Story/game/zz_selector.rpy +++ b/Monika After Story/game/zz_selector.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## module that contains a workable selection screen? # # NOTE: i have no idea how generic this can get. @@ -1167,7 +1168,7 @@ init -10 python in mas_selspr: OUT: select_map - select map cleaned of non-selectd items """ - for item_name in select_map.keys(): + for item_name in tuple(select_map.keys()): if force or not select_map[item_name].selected: item = select_map.pop(item_name) item.selected = False # force deselection @@ -2411,7 +2412,7 @@ init -1 python: st - st for renpy render at - at for renpy render - RETURNS: rendered display name + RETURNS: rendered display name """ return renpy.render(disp_text, 1000, self.TOP_FRAME_CHUNK, st, at) @@ -3284,7 +3285,7 @@ label mas_selector_sidebar_select_confirm: monika_chr.restore(prev_moni_state) # If monika is wearing a remover ACS, remove it. - for item_name in select_map.keys(): + for item_name in tuple(select_map.keys()): sel_obj = select_map[item_name].selectable if sel_obj.remover: spr_obj = sel_obj.get_sprobj() diff --git a/Monika After Story/game/zz_spritedeco.rpy b/Monika After Story/game/zz_spritedeco.rpy index 6d6c9b4982..4e332e59a6 100644 --- a/Monika After Story/game/zz_spritedeco.rpy +++ b/Monika After Story/game/zz_spritedeco.rpy @@ -1,4 +1,4 @@ - +rpy python 3 # large rewrite incoming init -700 python in mas_deco: diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index 9bcd43c0d9..df0b153da8 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module for turning json formats into sprite objects # NOTE: This DEPENDS on sprite-chart.rpy and sprite-chart-matrix.rpy # @@ -2138,7 +2139,7 @@ init 189 python in mas_sprites_json: dry_run = True # get rid of __keys - for jkey in jobj.keys(): + for jkey in tuple(jobj.keys()): if jkey.startswith("__"): jobj.pop(jkey) @@ -2555,7 +2556,7 @@ init 189 python in mas_sprites_json: frs_gifts = store.persistent._mas_filereacts_sprite_gifts msj_gifts = store.persistent._mas_sprites_json_gifted_sprites - for giftname in frs_gifts.keys(): + for giftname in tuple(frs_gifts.keys()): if giftname in giftname_map: # overwrite the gift data if in here frs_gifts[giftname] = giftname_map[giftname] diff --git a/Monika After Story/game/zz_windowreacts.rpy b/Monika After Story/game/zz_windowreacts.rpy index 1a552f70df..b7d5555466 100644 --- a/Monika After Story/game/zz_windowreacts.rpy +++ b/Monika After Story/game/zz_windowreacts.rpy @@ -1,3 +1,4 @@ +rpy python 3 #NOTE: This ONLY works for Windows atm #Whether Monika can use notifications or not @@ -671,7 +672,7 @@ label mas_wrs_twitter: "Anything interesting to share, [player]?": False, "280 characters? I only need [temp_len]...\n[temp_line]": True } - quip = renpy.random.choice(ily_quips_map.keys()) + quip = renpy.random.choice(tuple(ily_quips_map.keys())) wrs_success = display_notif( m_name, From bf0392033e49157aaf0ce11c019ae518ba93c126 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 28 Dec 2020 17:45:47 -0500 Subject: [PATCH 017/180] handle the rest of the cases --- Monika After Story/game/definitions.rpy | 23 +++++++++---------- Monika After Story/game/dev/dev_db.rpy | 15 ++++++------ .../game/dev/dev_exp_previewer.rpy | 2 +- Monika After Story/game/dev/dev_greetings.rpy | 2 +- Monika After Story/game/dev/dev_pg_topics.rpy | 18 +++++++-------- Monika After Story/game/dev/dev_weather.rpy | 7 +++--- Monika After Story/game/event-handler.rpy | 8 +++---- Monika After Story/game/import_ddlc.rpy | 2 +- Monika After Story/game/main_menu.rpy | 1 + Monika After Story/game/options.rpy | 1 + Monika After Story/game/pong.rpy | 1 + Monika After Story/game/script-apologies.rpy | 2 +- Monika After Story/game/script-ch30.rpy | 6 ++--- Monika After Story/game/script-farewells.rpy | 4 ++-- Monika After Story/game/script-fun-facts.rpy | 2 +- Monika After Story/game/script-greetings.rpy | 2 +- Monika After Story/game/script-holidays.rpy | 5 ++-- Monika After Story/game/script-moods.rpy | 2 +- Monika After Story/game/script-songs.rpy | 10 ++++---- Monika After Story/game/script-stories.rpy | 3 ++- .../game/script-story-events.rpy | 1 + Monika After Story/game/script-topics.rpy | 4 ++-- Monika After Story/game/script.rpy | 1 + .../game/sprite-chart-matrix.rpy | 8 +++---- Monika After Story/game/sprite-chart.rpy | 10 ++++---- Monika After Story/game/styles.rpy | 8 +++---- Monika After Story/game/updates.rpy | 18 +++++++-------- Monika After Story/game/zz_backgrounds.rpy | 14 +++++------ Monika After Story/game/zz_consumables.rpy | 14 +++++------ Monika After Story/game/zz_games.rpy | 6 ++--- Monika After Story/game/zz_history.rpy | 10 ++++---- Monika After Story/game/zz_interactions.rpy | 22 +++++++++--------- Monika After Story/game/zz_pianokeys.rpy | 2 +- Monika After Story/game/zz_poemgame.rpy | 2 +- Monika After Story/game/zz_poems.rpy | 10 ++++---- Monika After Story/game/zz_reactions.rpy | 6 ++--- Monika After Story/game/zz_seasons.rpy | 2 +- Monika After Story/game/zz_selector.rpy | 12 +++++----- Monika After Story/game/zz_spritejsons.rpy | 6 ++--- Monika After Story/game/zz_submods.rpy | 12 +++++----- Monika After Story/game/zz_weather.rpy | 10 ++++---- Monika After Story/game/zz_windowreacts.rpy | 4 ++-- .../Backgrounds/game/dev/test_bwswap.rpy | 4 ++-- 43 files changed, 154 insertions(+), 148 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index b5a6eebb91..0e4960f10c 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -125,7 +125,7 @@ python early: if object_name in scope: stores_names_list = [ store_module_name - for store_module_name, store_module in sys.modules.iteritems() + for store_module_name, store_module in sys.modules.items() if store_module and store_module.__dict__ is scope ] if stores_names_list: @@ -694,8 +694,7 @@ python early: # if the lock exists, then iterate through the names # and only update items that are unlocked - for name,index in Event.T_EVENT_NAMES.iteritems(): - + for name,index in Event.T_EVENT_NAMES.items(): if not lock_entry[index]: stored_data_list[index] = data_row[index] @@ -1463,7 +1462,7 @@ python early: filt_ev_dict = dict() # python 2 - for k,v in events.iteritems(): + for k,v in events.items(): # time to apply filtering rules if Event._filterEvent(v, **flt_args): filt_ev_dict[k] = v @@ -1547,7 +1546,7 @@ python early: _now = datetime.datetime.now() - for ev_label,ev in events.iteritems(): + for ev_label,ev in events.items(): # TODO: honestly, we should index events with conditionals # so we only check what needs to be checked. Its a bit of an # annoyance to check all of these properties once per minute. @@ -1715,7 +1714,7 @@ python early: _now = datetime.datetime.now() - for ev_label,ev in ev_dict.iteritems(): + for ev_label,ev in ev_dict.items(): # TODO: same TODO as in checkConditionals. # indexing would be smarter. @@ -1803,7 +1802,7 @@ python early: available_events = dict() # iterate over each event in the given events dict - for label, event in events.iteritems(): + for label, event in events.items(): if Event._checkRepeatRule(event, check_time, defval=False): if event.monikaWantsThisFirst(): @@ -1852,7 +1851,7 @@ python early: available_events = dict() # iterate over each event in the given events dict - for label, event in events.iteritems(): + for label, event in events.items(): # check if the event contains a MASFarewellRule and evaluate it if Event._checkFarewellRule(event): @@ -1906,7 +1905,7 @@ python early: available_events = dict() # iterate over each event in the given events dict - for label, event in events.iteritems(): + for label, event in events.items(): # check if the event contains a MASAffectionRule and evaluate it if Event._checkAffectionRule(event,keepNoRule=keepNoRule): @@ -3513,7 +3512,7 @@ python early: RETURNS: iter of ex prop names and values """ - return (item for item in self.ex_props.iteritems()) + return (item for item in self.ex_props.items()) def ex_pop(self, key, default=None): """ @@ -3546,7 +3545,7 @@ python early: props = [ "{0}: {1}".format(key, value) - for key, value in ex_props.iteritems() + for key, value in ex_props.items() ] return "".format(", ".join(props)) @@ -3812,7 +3811,7 @@ init -995 python in mas_utils: """ # check dicts if data is not None: - for value in data.itervalues(): + for value in data.values(): if value is not None: return False diff --git a/Monika After Story/game/dev/dev_db.rpy b/Monika After Story/game/dev/dev_db.rpy index b195d9500c..5c447ae849 100644 --- a/Monika After Story/game/dev/dev_db.rpy +++ b/Monika After Story/game/dev/dev_db.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## special functions to check integrity of systems init python: @@ -6,7 +7,7 @@ init python: if val is not None and not isinstance(val, bool): report.extend([delim, "bad ", name, " {0}".format(val)]) - + def _mas_check_ev_type_dict(val, name, report, delim=" | ", str_rep=True): if val is not None and not isinstance(val, dict): report.extend([delim, "bad ", name, " {0}".format(val)]) @@ -29,7 +30,7 @@ init python: def _mas_check_ev_type_str(val, name, report, delim=" | ", str_rep=True): if ( - val is not None + val is not None and not (isinstance(val, str) or isinstance(val, unicode)) ): report.extend([delim, "bad ", name, " {0}".format(val)]) @@ -37,7 +38,7 @@ init python: def _mas_check_ev_type_tuli(val, name, report, delim=" | ", str_rep=True): if ( - val is not None + val is not None and not ( isinstance(val, list) or isinstance(val, tuple) @@ -132,7 +133,7 @@ init python: def mas_check_event_types(per_db, str_buffer=None, str_rep=True): """ - Goes through given persistent database for events and double checks + Goes through given persistent database for events and double checks types. Returns a string report. IN: @@ -147,8 +148,8 @@ init python: # NOTE: we assume lots of things about the given per_db. if str_buffer is None: return - - for ev_label, ev_line in per_db.iteritems(): + + for ev_label, ev_line in per_db.items(): str_buffer.write("".join(_mas_check_ev_type_per(ev_line))) def mas_largest_persistent_item(): @@ -187,7 +188,7 @@ init python: mas_per_dump_list(item_key) # NOTE: ignore others for now - + def mas_per_dump_dict(dkey): """ Dumps an output of a persistent dict diff --git a/Monika After Story/game/dev/dev_exp_previewer.rpy b/Monika After Story/game/dev/dev_exp_previewer.rpy index c23de1c0a1..f43a131524 100644 --- a/Monika After Story/game/dev/dev_exp_previewer.rpy +++ b/Monika After Story/game/dev/dev_exp_previewer.rpy @@ -498,7 +498,7 @@ init 999 python: # update torsos with spritepacked sprites torso_map = self.SEL_TX_MAP["torso"] torso_list = self.SC_MAP["torso"] - for sel in store.mas_selspr.CLOTH_SEL_MAP.itervalues(): + for sel in store.mas_selspr.CLOTH_SEL_MAP.values(): spr = sel.get_sprobj() if spr.is_custom and spr.name not in torso_map: torso_map[spr.name] = sel.display_name diff --git a/Monika After Story/game/dev/dev_greetings.rpy b/Monika After Story/game/dev/dev_greetings.rpy index 89a05057e4..cf6de399ba 100644 --- a/Monika After Story/game/dev/dev_greetings.rpy +++ b/Monika After Story/game/dev/dev_greetings.rpy @@ -255,7 +255,7 @@ label dev_gre_sampler: # done with sampling, output results with open(renpy.config.basedir + "/gre_sample", "w") as outdata: - for ev_label, count in results.iteritems(): + for ev_label, count in results.items(): outdata.write("{0},{1}\n".format(ev_label, count)) # relock locked gres diff --git a/Monika After Story/game/dev/dev_pg_topics.rpy b/Monika After Story/game/dev/dev_pg_topics.rpy index 71b5c16c53..36df366903 100644 --- a/Monika After Story/game/dev/dev_pg_topics.rpy +++ b/Monika After Story/game/dev/dev_pg_topics.rpy @@ -18,7 +18,7 @@ label zz_mas_poemgame_actone: m "Hi [player]!" m "These are your point totals:" python: - for k,v in testvalues.iteritems(): + for k,v in testvalues.items(): m(k + " received "+ str(v) + " pt(s) from your choices.") m "I hope that was fun!" @@ -43,7 +43,7 @@ label zz_mas_poemgame_acttwo: m "Hi [player]!" m "These are your point totals:" python: - for k,v in testvalues.iteritems(): + for k,v in testvalues.items(): m(k + " received "+ str(v) + " pt(s) from your choices.") m "I hope that was fun!" @@ -119,7 +119,7 @@ label zz_mas_poemgame_actonept: m "Hi [player]!" m "These are your point totals:" python: - for k,v in testvalues.iteritems(): + for k,v in testvalues.items(): m(k + " received "+ str(v) + " pt(s) from your choices.") m "And you selected these words:" @@ -279,7 +279,7 @@ label zz_mas_poemgame_oneg: m "Hi [player]!" m "These are your point totals:" python: - for k,v in testvalues.iteritems(): + for k,v in testvalues.items(): m(k + " received "+ str(v) + " pt(s) from your choices.") @@ -325,7 +325,7 @@ label zz_mas_poemgame_oc: m "Hi [player]!" m "These are your point totals:" python: - for k,v in testvalues.iteritems(): + for k,v in testvalues.items(): m(k + " received "+ str(v) + " pt(s) from your choices.") m "you selected these words:" python: @@ -364,7 +364,7 @@ label zz_mas_pg_tb_grid_t1: "three", "four" ] - + # area/positiong of grid xywh = (640, 10, 600, 400) row_info = (4, None) @@ -388,7 +388,7 @@ label zz_mas_pg_tb_grid_t1: m 1a "You picked '[picked]'" show monika at t11 return - + # this label tests out the grid with bg label zz_mas_pg_tb_grid_t2: show monika at t22 @@ -414,7 +414,7 @@ label zz_mas_pg_tb_grid_t2: xywh = (10, 10, 400, 500) # using NOne for spacing should auto calc the spacing - row_info = (6, 50) + row_info = (6, 50) col_info = (2, 250) # generate the word:returnvalue tuples @@ -436,4 +436,4 @@ label zz_mas_pg_tb_grid_t2: $ picked = _return m 1a "You picked '[picked]'" show monika at t11 - return + return diff --git a/Monika After Story/game/dev/dev_weather.rpy b/Monika After Story/game/dev/dev_weather.rpy index 7de555dd41..724b52163f 100644 --- a/Monika After Story/game/dev/dev_weather.rpy +++ b/Monika After Story/game/dev/dev_weather.rpy @@ -27,7 +27,7 @@ label dev_change_weather: # build other weather list other_weathers = [ (mw_obj.prompt, mw_obj, False, False) - for mw_id, mw_obj in mas_weather.WEATHER_MAP.iteritems() + for mw_id, mw_obj in mas_weather.WEATHER_MAP.items() if mw_id != "def" ] @@ -99,7 +99,7 @@ label dev_weather_sampler: for count in range(sample_size): got_weather = mas_shouldRain() totals += 1 - + if got_weather is None: results["default"] += 1 @@ -111,7 +111,7 @@ label dev_weather_sampler: # done with sampling, output results with open(renpy.config.basedir + "/weather_sample", "w") as outdata: - for weather_name, count in results.iteritems(): + for weather_name, count in results.items(): outdata.write("{0},{1} -> {2}\n".format( weather_name, count, @@ -120,4 +120,3 @@ label dev_weather_sampler: m "check files for 'weather_sample' for more info." return - diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 3546c48eb7..4434eefdb1 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -383,7 +383,7 @@ init 6 python: # mainly to create centralized database for calendar lookup # (and possible general db lookups) mas_all_ev_db = {} - for code,ev_db in mas_all_ev_db_map.iteritems(): + for code,ev_db in mas_all_ev_db_map.items(): mas_all_ev_db.update(ev_db) del code, ev_db @@ -571,7 +571,7 @@ init 6 python: if ev is None: return False - for attr, new_value in kwargs.iteritems(): + for attr, new_value in kwargs.items(): setattr(ev, attr, new_value) return True @@ -2349,7 +2349,7 @@ init python: # get locked pool topics that are not banned from unlocking pool_evs = [ ev - for ev in evhand.event_database.itervalues() + for ev in evhand.event_database.values() if ( Event._filterEvent(ev, unlocked=False, pool=True) and "no_unlock" not in ev.rules @@ -3025,7 +3025,7 @@ label mas_bookmarks_unbookmark(bookmarks_items): # sanity check that the user selected something if bookmarks_to_remove: python: - for ev_label in bookmarks_to_remove.iterkeys(): + for ev_label in bookmarks_to_remove.keys(): # remove the bookmark from persist (if in it) if ev_label in persistent._mas_player_bookmarked: persistent._mas_player_bookmarked.remove(ev_label) diff --git a/Monika After Story/game/import_ddlc.rpy b/Monika After Story/game/import_ddlc.rpy index 66dd2d84a5..f44cd6a97e 100644 --- a/Monika After Story/game/import_ddlc.rpy +++ b/Monika After Story/game/import_ddlc.rpy @@ -15,7 +15,7 @@ init python: fo = open(dumppath, "w") - for key in sorted(dumped_persistent.iterkeys()): + for key in sorted(dumped_persistent.keys()): fo.write(str(key) + ' - ' + str(type(dumped_persistent[key])) + ' >>> '+ str(dumped_persistent[key]) + '\n\n') fo.close() diff --git a/Monika After Story/game/main_menu.rpy b/Monika After Story/game/main_menu.rpy index 91518b18a8..54b76c44e9 100644 --- a/Monika After Story/game/main_menu.rpy +++ b/Monika After Story/game/main_menu.rpy @@ -1,3 +1,4 @@ +rpy python 3 screen main_menu(): # This ensures that any other menu screen is replaced. diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 3b7b0e54a6..eb6f722989 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## This file contains options that can be changed to customize your game. ## ## Lines beginning with two '#' marks are comments, and you shouldn't uncomment diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index 6c81b70cf0..cd535c880e 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -1,3 +1,4 @@ +rpy python 3 # pong difficulty changes on win / loss. Determines monika's paddle-movement-cap, the ball's start-speed, max-speed and acceleration. default persistent._mas_pong_difficulty = 10 # increases the pong difficulty for the next game by the value this is set to. Resets after a finished match. diff --git a/Monika After Story/game/script-apologies.rpy b/Monika After Story/game/script-apologies.rpy index c53e9c263d..525436bb2e 100644 --- a/Monika After Story/game/script-apologies.rpy +++ b/Monika After Story/game/script-apologies.rpy @@ -94,7 +94,7 @@ label monika_playerapologizes: python: apologylist = [ (ev.prompt, ev.eventlabel, False, False) - for ev_label, ev in store.mas_apology.apology_db.iteritems() + for ev_label, ev in store.mas_apology.apology_db.items() if ev.unlocked and (ev.prompt != "...for something." and ev.prompt != "...for something else.") ] diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 15a3c721c3..2c7f537dc7 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -882,10 +882,10 @@ label spaceroom(start_bg=None, hide_mask=None, hide_monika=False, dissolve_all=F # add show/hide statements for decos if bg_change_info is not None: if not scene_change: - for h_adf in bg_change_info.hides.itervalues(): + for h_adf in bg_change_info.hides.values(): h_adf.hide() - for s_tag, s_adf in bg_change_info.shows.iteritems(): + for s_tag, s_adf in bg_change_info.shows.items(): s_adf.show(s_tag) # vignette @@ -1799,7 +1799,7 @@ label ch30_reset: } mas_unlockGame("pong") # always unlock pong - for game_name, game_startlabel in game_unlock_db.iteritems(): + for game_name, game_startlabel in game_unlock_db.items(): # unlock if we've seen the label if mas_getEVL_shown_count(game_startlabel) > 0: mas_unlockGame(game_name) diff --git a/Monika After Story/game/script-farewells.rpy b/Monika After Story/game/script-farewells.rpy index 908ee1e1f3..317d6ce79f 100644 --- a/Monika After Story/game/script-farewells.rpy +++ b/Monika After Story/game/script-farewells.rpy @@ -142,7 +142,7 @@ init -1 python in mas_farewells: check_time = datetime.datetime.now() # now filter - for ev_label, ev in fare_db.iteritems(): + for ev_label, ev in fare_db.items(): if _filterFarewell( ev, curr_priority, @@ -198,7 +198,7 @@ label mas_farewell_start: # build a prompt list bye_prompt_list = sorted([ (ev.prompt, ev, False, False) - for k,ev in bye_pool_events.iteritems() + for k,ev in bye_pool_events.items() ]) most_used_fare = sorted(bye_pool_events.values(), key=Event.getSortShownCount)[-1] diff --git a/Monika After Story/game/script-fun-facts.rpy b/Monika After Story/game/script-fun-facts.rpy index 35bd34c577..953baab286 100644 --- a/Monika After Story/game/script-fun-facts.rpy +++ b/Monika After Story/game/script-fun-facts.rpy @@ -14,7 +14,7 @@ init -10 python in mas_fun_facts: """ return [ fun_fact_evl - for fun_fact_evl, ev in fun_fact_db.iteritems() + for fun_fact_evl, ev in fun_fact_db.items() if not ev.unlocked ] diff --git a/Monika After Story/game/script-greetings.rpy b/Monika After Story/game/script-greetings.rpy index 99523b5b6e..648fb260d2 100644 --- a/Monika After Story/game/script-greetings.rpy +++ b/Monika After Story/game/script-greetings.rpy @@ -230,7 +230,7 @@ init -1 python in mas_greetings: check_time = datetime.datetime.now() # now filter - for ev_label, ev in gre_db.iteritems(): + for ev_label, ev in gre_db.items(): if _filterGreeting( ev, curr_priority, diff --git a/Monika After Story/game/script-holidays.rpy b/Monika After Story/game/script-holidays.rpy index c725583f92..24533b2eab 100644 --- a/Monika After Story/game/script-holidays.rpy +++ b/Monika After Story/game/script-holidays.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## holiday info goes here # # TOC @@ -1657,7 +1658,7 @@ init -10 python in mas_d25_utils: # save remaining d25 gifts and delete the packages # they will be reacted to later - for c_gift_name, gift_name in d25_map.iteritems(): + for c_gift_name, gift_name in d25_map.items(): #Only add if the gift isn't already stored under the tree if c_gift_name not in store.persistent._mas_d25_gifts_given: store.persistent._mas_d25_gifts_given.append(c_gift_name) @@ -1666,7 +1667,7 @@ init -10 python in mas_d25_utils: store.mas_docking_station.destroyPackage(gift_name) # set all excluded and generic gifts to react now - for c_gift_name, mas_gift in found_map.iteritems(): + for c_gift_name, mas_gift in found_map.items(): store.persistent._mas_filereacts_reacted_map[c_gift_name] = mas_gift # register these gifts diff --git a/Monika After Story/game/script-moods.rpy b/Monika After Story/game/script-moods.rpy index ff120b9f73..d09fbb00c5 100644 --- a/Monika After Story/game/script-moods.rpy +++ b/Monika After Story/game/script-moods.rpy @@ -638,7 +638,7 @@ label mas_mood_bored: python: unlockedgames = [ game_ev.prompt.lower() - for game_ev in mas_games.game_db.itervalues() + for game_ev in mas_games.game_db.values() if mas_isGameUnlocked(game_ev.prompt) ] diff --git a/Monika After Story/game/script-songs.rpy b/Monika After Story/game/script-songs.rpy index 3dac71aa3e..5d2d11948e 100644 --- a/Monika After Story/game/script-songs.rpy +++ b/Monika After Story/game/script-songs.rpy @@ -76,14 +76,14 @@ init python in mas_songs: if length is None: return [ (ev.prompt, ev_label, False, False) - for ev_label, ev in song_db.iteritems() + for ev_label, ev in song_db.items() if ev.unlocked ] else: return [ (ev.prompt, ev_label, False, False) - for ev_label, ev in song_db.iteritems() + for ev_label, ev in song_db.items() if ev.unlocked and length in ev.category ] @@ -100,7 +100,7 @@ init python in mas_songs: if unseen_only: return [ ev_label - for ev_label, ev in song_db.iteritems() + for ev_label, ev in song_db.items() if ( not store.seen_event(ev_label) and ev.random @@ -111,7 +111,7 @@ init python in mas_songs: return [ ev_label - for ev_label, ev in song_db.iteritems() + for ev_label, ev in song_db.items() if ev.random and TYPE_SHORT in ev.category and ev.checkAffection(store.mas_curr_affection) ] @@ -144,7 +144,7 @@ init python in mas_songs: return [ (ev.prompt, ev_label, False, False) - for ev_label, ev in song_db.iteritems() + for ev_label, ev in song_db.items() if ev.unlocked and TYPE_ANALYSIS in ev.category and ev.checkAffection(curr_aff) ] diff --git a/Monika After Story/game/script-stories.rpy b/Monika After Story/game/script-stories.rpy index 902daa24e3..304ab25639 100644 --- a/Monika After Story/game/script-stories.rpy +++ b/Monika After Story/game/script-stories.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module for Monika story telling # # Stories will get unlocked one at by session @@ -34,7 +35,7 @@ init -1 python in mas_stories: renpy.store.mas_stories.story_database, unlocked=False ) - for _, story in stories.iteritems(): + for story in stories.values(): story.unlocked = True diff --git a/Monika After Story/game/script-story-events.rpy b/Monika After Story/game/script-story-events.rpy index 442331bede..d29d57dd81 100644 --- a/Monika After Story/game/script-story-events.rpy +++ b/Monika After Story/game/script-story-events.rpy @@ -1,3 +1,4 @@ +rpy python 3 #This file will include short story events that don't require their own file. #An event is crated by only adding a label and adding a requirement (see comment below). diff --git a/Monika After Story/game/script-topics.rpy b/Monika After Story/game/script-topics.rpy index bc2262a7b1..dbe4fd2c37 100644 --- a/Monika After Story/game/script-topics.rpy +++ b/Monika After Story/game/script-topics.rpy @@ -666,7 +666,7 @@ init python in mas_bookmarks_derand: #Firstly, let's get our derandom keys derand_keys = [ label_prefix_data["derand_persist_key"] - for label_prefix_data in label_prefix_map.itervalues() + for label_prefix_data in label_prefix_map() if "derand_persist_key" in label_prefix_data ] @@ -732,7 +732,7 @@ label mas_rerandom: show monika at t11 python: - for ev_label in topics_to_rerandom.iterkeys(): + for ev_label in topics_to_rerandom.keys(): #Get the ev rerand_ev = mas_getEV(ev_label) diff --git a/Monika After Story/game/script.rpy b/Monika After Story/game/script.rpy index 28b955a0a2..f4b9fc7220 100644 --- a/Monika After Story/game/script.rpy +++ b/Monika After Story/game/script.rpy @@ -1,3 +1,4 @@ +rpy python 3 # This is used for top-level game strucutre. # Should not include any actual events or scripting; only logic and calling other labels. # diff --git a/Monika After Story/game/sprite-chart-matrix.rpy b/Monika After Story/game/sprite-chart-matrix.rpy index 3cc336c0f0..fed1b01eb8 100644 --- a/Monika After Story/game/sprite-chart-matrix.rpy +++ b/Monika After Story/game/sprite-chart-matrix.rpy @@ -225,7 +225,7 @@ python early: img = renpy.substitute(img) args = [] - for flt in store.mas_sprites.FILTERS.iterkeys(): + for flt in store.mas_sprites.FILTERS.keys(): # condition args.append("store.mas_sprites.get_filter() == '{0}'".format(flt)) @@ -292,7 +292,7 @@ python early: if filterize_def: # default should be filterized - for flt in store.mas_sprites.FILTERS.iterkeys(): + for flt in store.mas_sprites.FILTERS.keys(): # only use the filtesr we have not already added if flt not in flt_pairs: @@ -529,7 +529,7 @@ init 1 python in mas_sprites: Raises all errors. """ - for mfwm_id, mfwm in FW_DB.iteritems(): + for mfwm_id, mfwm in FW_DB.items(): _verify_mfwm(mfwm_id, mfwm) @@ -1028,7 +1028,7 @@ init -4 python in mas_sprites: """ Clears all caches """ - for cid, cache in CACHE_TABLE.iteritems(): + for cid, cache in CACHE_TABLE.items(): for key in tuple(cache.keys()): cache.pop(key) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index 8675ad4e3e..a2031066e6 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -944,7 +944,7 @@ init -5 python in mas_sprites: # reverse map for eaiser lookup ARMS_LEAN = {} - for lean, values in LEAN_ARMS.iteritems(): + for lean, values in LEAN_ARMS.items(): for value in values: ARMS_LEAN[value] = lean @@ -4238,7 +4238,7 @@ init -3 python: # loop over valid arm data isbad = False - for arm_id, arm_sid in store.mas_sprites.NUM_ARMS.iteritems(): + for arm_id, arm_sid in store.mas_sprites.NUM_ARMS.items(): if arm_sid in json_obj: arm_obj = json_obj.pop(arm_sid) @@ -4861,13 +4861,13 @@ init -3 python: """ try: values = [] - for value in self.__all_map.itervalues(): + for value in self.__all_map.values(): if value is not None and value not in values: values.append(value) return values except: - return self.values() + return list(self.values()) def values(self): """ @@ -4877,7 +4877,7 @@ init -3 python: """ return [ value - for value in self.__all_map.itervalues() + for value in self.__all_map.values() if value is not None ] diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index 896f866466..a2619c701d 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -165,7 +165,7 @@ init python: # FIXME: could be done on startup for some speedup new_aliases = {} - for style_tuple, style_ptr in renpy.style.styles.iteritems(): + for style_tuple, style_ptr in renpy.style.styles.items(): style_name = style_tuple[0] if mas_isTextDarkStyle(style_name): text_dark_suffix = "_text" + mas_ui.dark_suffix @@ -174,7 +174,7 @@ init python: if not style.exists(alias_name): new_aliases[alias_name] = style_ptr - for alias_name, alias_style_ptr in new_aliases.iteritems(): + for alias_name, alias_style_ptr in new_aliases.items(): setattr(style, alias_name, alias_style_ptr) # Automagically switch every style which has a dark variant @@ -370,7 +370,7 @@ init -10 python in mas_ui: OUT: dict of key-value pairs """ - return {item[0]: item[1]["return_value"] for item in buttons_data.iteritems() if item[1]["return_value"] == item[1]["true_value"] or return_all} + return {item[0]: item[1]["return_value"] for item in buttons_data.items() if item[1]["return_value"] == item[1]["true_value"] or return_all} def check_scr_menu_choose_prompt(buttons_data, selected_prompt, default_prompt): """ @@ -384,7 +384,7 @@ init -10 python in mas_ui: OUT: string with prompt """ - for data in buttons_data.itervalues(): + for data in buttons_data.values(): if data["return_value"] == data["true_value"]: return selected_prompt return default_prompt diff --git a/Monika After Story/game/updates.rpy b/Monika After Story/game/updates.rpy index d4f0ae6031..4fcd8d471d 100644 --- a/Monika After Story/game/updates.rpy +++ b/Monika After Story/game/updates.rpy @@ -635,7 +635,7 @@ label v0_11_4(version="v0_11_4"): label v0_11_3(version="v0_11_3"): python: #Rerandom all songs which aren't d25 exclusive - for song_ev in mas_songs.song_db.itervalues(): + for song_ev in mas_songs.song_db.values(): if ( song_ev.eventlabel not in ["mas_song_aiwfc", "mas_song_merry_christmas_baby"] and mas_songs.TYPE_LONG not in song_ev.category @@ -651,7 +651,7 @@ label v0_11_3(version="v0_11_3"): persistent._mas_pool_unlocks += store.mas_xp.level() * 4 #Adjust consumables to be at their max stock amount - for consumable_id in persistent._mas_consumable_map.iterkeys(): + for consumable_id in persistent._mas_consumable_map.keys(): cons = mas_getConsumable(consumable_id) if cons and cons.getStock() > cons.max_stock_amount: @@ -885,7 +885,7 @@ label v0_11_1(version="v0_11_1"): label v0_11_0(version="v0_11_0"): python: #First, we're fixing the consumables map - for cons_id in persistent._mas_consumable_map.iterkeys(): + for cons_id in persistent._mas_consumable_map.keys(): persistent._mas_consumable_map[cons_id]["has_restock_warned"] = False #Let's stock current users on some consumables (assuming they've gifted before) @@ -951,7 +951,7 @@ label v0_11_0(version="v0_11_0"): "greeting_hamlet": "store.mas_getAbsenceLength() >= datetime.timedelta(days=7)" } - for gr_label, conditional in new_greetings_conditions.iteritems(): + for gr_label, conditional in new_greetings_conditions.items(): gr_ev = mas_getEV(gr_label) if gr_ev: gr_ev.conditional = conditional @@ -990,7 +990,7 @@ label v0_11_0(version="v0_11_0"): "monika_changename": "mas_preferredname" } - for new_evl, old_evl in topic_transfer_map.iteritems(): + for new_evl, old_evl in topic_transfer_map.items(): mas_transferTopicData(new_evl, old_evl, persistent.event_database) #If we've seen this event before, then we shouldn't allow its conditions to be true again @@ -1170,7 +1170,7 @@ label v0_10_6(version="v0_10_6"): persistent._mas_history_archives[year]["player_bday.saw_surprise"] = True #Give unseen fun facts the unlocked prop - for ev in mas_fun_facts.fun_fact_db.itervalues(): + for ev in mas_fun_facts.fun_fact_db.values(): if ev.shown_count: ev.unlocked = True @@ -1261,7 +1261,7 @@ label v0_10_5(version="v0_10_5"): "mas_bad_facts_4": "mas_bad_fact_tree_moss", } - for old_evl, new_evl in fun_facts_evls.iteritems(): + for old_evl, new_evl in fun_facts_evls.items(): mas_transferTopicData( new_evl, old_evl, @@ -1288,7 +1288,7 @@ label v0_10_5(version="v0_10_5"): "mas_monika_daynight2": "mas_island_daynight2" } - for old_label, new_label in islands_evs.iteritems(): + for old_label, new_label in islands_evs.items(): mas_transferTopicSeen(old_label, new_label) #Fix these persist vars @@ -2013,7 +2013,7 @@ label v0_9_0(version="v0_9_0"): mas_bd_ev.action = EV_ACT_QUEUE # remove random props from all greetings - for gre_label, gre_ev in store.evhand.greeting_database.iteritems(): + for gre_label, gre_ev in store.evhand.greeting_database.items(): # hopefully we never use random in greetings ever gre_ev.random = False diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index cf2e021823..7681b084bb 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -2654,7 +2654,7 @@ init -20 python in mas_background: """ Builds all background objects using current time settings. """ - for flt_bg in BACKGROUND_MAP.itervalues(): + for flt_bg in BACKGROUND_MAP.values(): flt_bg.build() @@ -2713,7 +2713,7 @@ init -20 python in mas_background: if store.persistent._mas_background_MBGdata is None: return - for mbg_id, mbg_data in store.persistent._mas_background_MBGdata.iteritems(): + for mbg_id, mbg_data in store.persistent._mas_background_MBGdata.items(): mbg_obj = BACKGROUND_MAP.get(mbg_id, None) if mbg_obj is not None: mbg_obj.fromTuple(mbg_data) @@ -2722,7 +2722,7 @@ init -20 python in mas_background: """ Saves MASBackground data from weather map into persistent """ - for mbg_id, mbg_obj in BACKGROUND_MAP.iteritems(): + for mbg_id, mbg_obj in BACKGROUND_MAP.items(): store.persistent._mas_background_MBGdata[mbg_id] = mbg_obj.toTuple() def getUnlockedBGCount(): @@ -2730,7 +2730,7 @@ init -20 python in mas_background: Gets the number of unlocked backgrounds """ unlocked_count = 0 - for mbg_obj in BACKGROUND_MAP.itervalues(): + for mbg_obj in BACKGROUND_MAP.values(): unlocked_count += int(mbg_obj.unlocked) return unlocked_count @@ -2745,7 +2745,7 @@ init -20 python in mas_background: True if we have at least min_amt_unlocked BGs unlocked, False otherwise """ unlocked_count = 0 - for mbg_obj in BACKGROUND_MAP.itervalues(): + for mbg_obj in BACKGROUND_MAP.values(): unlocked_count += int(mbg_obj.unlocked) #Now check if we've surpassed the minimum @@ -3115,7 +3115,7 @@ init -1 python: init 1 python in mas_background: # verify all backgrounds - for flt_bg in BACKGROUND_MAP.itervalues(): + for flt_bg in BACKGROUND_MAP.values(): flt_bg.verify() #START: Image definitions @@ -3179,7 +3179,7 @@ label monika_change_background_loop: # build other backgrounds list other_backgrounds = [ (mbg_obj.prompt, mbg_obj, False, False) - for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.iteritems() + for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.items() if mbg_id != "spaceroom" and mbg_obj.unlocked ] diff --git a/Monika After Story/game/zz_consumables.rpy b/Monika After Story/game/zz_consumables.rpy index b3a3b32882..a22ee06228 100644 --- a/Monika After Story/game/zz_consumables.rpy +++ b/Monika After Story/game/zz_consumables.rpy @@ -700,7 +700,7 @@ init 5 python: list of all consumables Monika is low on (or critical on) """ low_cons = [] - for _type in store.mas_consumables.consumable_map.iterkeys(): + for _type in store.mas_consumables.consumable_map.keys(): low_cons += MASConsumable._getLowConsType(_type, critical) return low_cons @@ -718,7 +718,7 @@ init 5 python: list of all consumables Monika """ low_cons = [] - for _type in store.mas_consumables.consumable_map.iterkeys(): + for _type in store.mas_consumables.consumable_map.keys(): low_cons += MASConsumable._getLowConsType(_type, critical, exclude_restock_warned=True) return low_cons @@ -745,14 +745,14 @@ init 5 python: if exclude_restock_warned: return [ cons - for cons in store.mas_consumables.consumable_map[_type].itervalues() + for cons in store.mas_consumables.consumable_map[_type].values() if cons.enabled() and cons.should_restock_warn and cons.isCriticalLow() and not cons.hasRestockWarned() ] else: return [ cons - for cons in store.mas_consumables.consumable_map[_type].itervalues() + for cons in store.mas_consumables.consumable_map[_type].values() if cons.enabled() and cons.should_restock_warn and cons.isCriticalLow() ] @@ -760,14 +760,14 @@ init 5 python: if exclude_restock_warned: return [ cons - for cons in store.mas_consumables.consumable_map[_type].itervalues() + for cons in store.mas_consumables.consumable_map[_type].values() if cons.enabled() and cons.should_restock_warn and cons.isLow() and not cons.hasRestockWarned() ] else: return [ cons - for cons in store.mas_consumables.consumable_map[_type].itervalues() + for cons in store.mas_consumables.consumable_map[_type].values() if cons.enabled() and cons.should_restock_warn and cons.isLow() ] @@ -912,7 +912,7 @@ init 5 python: return [ cons - for cons in mas_consumables.consumable_map[_type].itervalues() + for cons in mas_consumables.consumable_map[_type].values() if cons.enabled() and cons.hasServing() and cons.checkCanHave() and cons.isConsTime() ] diff --git a/Monika After Story/game/zz_games.rpy b/Monika After Story/game/zz_games.rpy index 0e92920f8b..0087af9e1e 100644 --- a/Monika After Story/game/zz_games.rpy +++ b/Monika After Story/game/zz_games.rpy @@ -32,7 +32,7 @@ init 1 python in mas_games: global game_db total_shown_count = 0 - for ev in game_db.itervalues(): + for ev in game_db.values(): if ev.eventlabel not in exclude_list: total_shown_count += ev.shown_count @@ -55,7 +55,7 @@ init 7 python in mas_games: gamename = gamename.lower() #Now search - for ev in game_db.itervalues(): + for ev in game_db.values(): if renpy.substitute(ev.prompt).lower() == gamename: return ev return None @@ -186,7 +186,7 @@ label mas_pick_a_game: #Now let's get all of the unlocked games at the aff level game_menuitems = sorted([ (ev.prompt, ev.eventlabel, False, False) - for ev in mas_games.game_db.itervalues() + for ev in mas_games.game_db.values() if mas_isGameUnlocked(renpy.substitute(ev.prompt)) ], key=lambda x:renpy.substitute(x[0])) diff --git a/Monika After Story/game/zz_history.rpy b/Monika After Story/game/zz_history.rpy index 5199fc2f81..dabbbebaa0 100644 --- a/Monika After Story/game/zz_history.rpy +++ b/Monika After Story/game/zz_history.rpy @@ -187,7 +187,7 @@ init -860 python in mas_history: found_data = lookup_otl(key, years_list) years_found = [] - for year, data_tuple in found_data.iteritems(): + for year, data_tuple in found_data.items(): status, _data = data_tuple if status == L_FOUND and _data == _verify: @@ -220,7 +220,7 @@ init -860 python in mas_history: ASSUMES: the mhs database is already filled """ - for mhs_id, mhs_data in store.persistent._mas_history_mhs_data.iteritems(): + for mhs_id, mhs_data in store.persistent._mas_history_mhs_data.items(): mhs = mhs_db.get(mhs_id, None) if mhs is not None: mhs.fromTuple(mhs_data) @@ -233,7 +233,7 @@ init -860 python in mas_history: """ Saves MASHistorySaver data from mhs_db into persistent """ - for mhs_id, mhs in mhs_db.iteritems(): + for mhs_id, mhs in mhs_db.items(): store.persistent._mas_history_mhs_data[mhs_id] = mhs.toTuple() @@ -780,7 +780,7 @@ init -850 python: save_year -= 1 # go through mapping and save data - for p_key, data_key in self.mapping.iteritems(): + for p_key, data_key in self.mapping.items(): # retrieve and save dest._store(source.get(p_key, None), data_key, save_year) @@ -834,7 +834,7 @@ init -800 python in mas_history: # is past today. _now = datetime.datetime.now() -# for mhs in mhs_db.itervalues(): +# for mhs in mhs_db.values(): for mhs in mhs_sorted_list: # trigger rules: # current date must be past trigger diff --git a/Monika After Story/game/zz_interactions.rpy b/Monika After Story/game/zz_interactions.rpy index 3a7fc7ad59..387cd54b62 100644 --- a/Monika After Story/game/zz_interactions.rpy +++ b/Monika After Story/game/zz_interactions.rpy @@ -100,8 +100,8 @@ init -10 python in mas_interactions: [1] zoom level [2] clickzone """ - for zl, zl_d in self._zoom_cz.iteritems(): - for zone_key, cz in zl_d.iteritems(): + for zl, zl_d in self._zoom_cz.items(): + for zone_key, cz in zl_d.items(): yield zone_key, zl, cz def _debug(self, value): @@ -147,7 +147,7 @@ init -10 python in mas_interactions: self._zones.pop(zone_key) # remove from zoom levels - for zone_d in self._zoom_cz.itervalues(): + for zone_d in self._zoom_cz.values(): if zone_key in zone_d: zone_d.pop(zone_key) @@ -159,7 +159,7 @@ init -10 python in mas_interactions: zone_key - key of the clickzone to change value - value to set disabled to """ - for zl_d in self._zoom_cz.itervalues(): + for zl_d in self._zoom_cz.values(): cz = zl_d.get(zone_key, None) if cz is not None: cz.disabled = value @@ -174,7 +174,7 @@ init -10 python in mas_interactions: # get zoom level dict containing clickzones zl_set = self._zoom_cz.get(zoom_level, {}) - for zone_key, cz in self._zones.iteritems(): + for zone_key, cz in self._zones.items(): # only add clickzones that dont already exist if zone_key not in zl_set: @@ -221,10 +221,10 @@ init -10 python in mas_interactions: ], ZONE_CHEST_1_R: [ (514, 453), # (her) right top - (491, 509), + (491, 509), (489, 533), (493, 551), - (498, 555), # (her) right to arm + (498, 555), # (her) right to arm (508, 498), (515, 453), ], @@ -467,7 +467,7 @@ init -9 python: start_zoom - pass this in if the clickzones are startnig at a zoom level that is not the current. (Default: None) - """ + """ if zone_actions is None: zone_actions = {} if zone_order is None: @@ -482,7 +482,7 @@ init -9 python: self._zones_unorder = {} self._last_zoom_level = start_zoom - + self._end_int = None self._rst_int = False self._jump_to = None @@ -592,7 +592,7 @@ init -9 python: def event(self, ev, x, y, st): """ - By default, we process events in order and return/jump as + By default, we process events in order and return/jump as appropriate. """ self.event_begin(ev, x, y, st) @@ -659,7 +659,7 @@ init -9 python: return r renders = [] - + # render in reverse zone order for visual clarity for zone_key, cz in self.zone_iter_r(): if not cz.disabled: diff --git a/Monika After Story/game/zz_pianokeys.rpy b/Monika After Story/game/zz_pianokeys.rpy index f9b92ac985..b33d3bced9 100644 --- a/Monika After Story/game/zz_pianokeys.rpy +++ b/Monika After Story/game/zz_pianokeys.rpy @@ -2850,7 +2850,7 @@ init 810 python: self.live_keymap = dict(mas_piano_keys.KEYMAP) # now apply adjustments - for key,real_key in persistent._mas_piano_keymaps.iteritems(): + for key,real_key in persistent._mas_piano_keymaps.items(): if ( real_key in self.live_keymap and real_key == self.live_keymap[real_key] diff --git a/Monika After Story/game/zz_poemgame.rpy b/Monika After Story/game/zz_poemgame.rpy index c19e8563d5..15ef852259 100644 --- a/Monika After Story/game/zz_poemgame.rpy +++ b/Monika After Story/game/zz_poemgame.rpy @@ -1416,7 +1416,7 @@ label mas_poem_minigame (flow,music_filename=audio.t4,show_monika=True, # figure out the winner largest = "" largestVal = 0 - for girl,points in points.iteritems(): + for girl,points in points.items(): if points > largestVal: largest = girl largestVal = points diff --git a/Monika After Story/game/zz_poems.rpy b/Monika After Story/game/zz_poems.rpy index 419a03f571..59b68b0e5e 100644 --- a/Monika After Story/game/zz_poems.rpy +++ b/Monika After Story/game/zz_poems.rpy @@ -59,14 +59,14 @@ init 11 python in mas_poems: if unseen: return [ poem - for poem in poem_map.itervalues() + for poem in poem_map.values() if not poem.is_seen() and poem.category == category ] #Otherwise we just get all return [ poem - for poem in poem_map.itervalues() + for poem in poem_map.values() if poem.category == category ] @@ -76,7 +76,7 @@ init 11 python in mas_poems: """ return sorted([ poem - for poem in poem_map.itervalues() + for poem in poem_map.values() if poem.is_seen() ], key=poem_sort_key) @@ -86,7 +86,7 @@ init 11 python in mas_poems: """ return sorted([ poem - for poem in poem_map.itervalues() + for poem in poem_map.values() if not poem.is_seen() ], key=poem_sort_key) @@ -112,7 +112,7 @@ init 11 python in mas_poems: """ return sorted([ (poem.prompt, poem, False, False) - for poem in poem_map.itervalues() + for poem in poem_map.values() if poem.is_seen() ], key=poem_menu_sort_key) diff --git a/Monika After Story/game/zz_reactions.rpy b/Monika After Story/game/zz_reactions.rpy index d383262a20..f0d5a1dd85 100644 --- a/Monika After Story/game/zz_reactions.rpy +++ b/Monika After Story/game/zz_reactions.rpy @@ -304,7 +304,7 @@ init -11 python in mas_filereacts: """ return [ giftname - for giftname, react_ev in filereact_map.iteritems() + for giftname, react_ev in filereact_map.items() if _key in react_ev.rules ] @@ -472,7 +472,7 @@ init -11 python in mas_filereacts: return [] # put the gifts in the reacted map - for c_gift_name, mas_gift in found_map.iteritems(): + for c_gift_name, mas_gift in found_map.items(): store.persistent._mas_filereacts_reacted_map[c_gift_name] = mas_gift found_gifts.sort() @@ -913,7 +913,7 @@ init python: """ return sorted([ _date - for _date, giftstat in persistent._mas_filereacts_historic.iteritems() + for _date, giftstat in persistent._mas_filereacts_historic.items() if giftlabel in giftstat ]) diff --git a/Monika After Story/game/zz_seasons.rpy b/Monika After Story/game/zz_seasons.rpy index d4bae29c71..36de03d705 100644 --- a/Monika After Story/game/zz_seasons.rpy +++ b/Monika After Story/game/zz_seasons.rpy @@ -224,7 +224,7 @@ init 10 python in mas_seasons: """ Determins the current season and returns appropriate season ID """ - for _id, logic in _season_logic_map.iteritems(): + for _id, logic in _season_logic_map.items(): if logic(): return _id diff --git a/Monika After Story/game/zz_selector.rpy b/Monika After Story/game/zz_selector.rpy index 1bcc5d185d..44ed011866 100644 --- a/Monika After Story/game/zz_selector.rpy +++ b/Monika After Story/game/zz_selector.rpy @@ -1000,7 +1000,7 @@ init -10 python in mas_selspr: ) # then readd everything that was previous - for item in add_map.itervalues(): + for item in add_map.values(): moni_chr.wear_acs(item.selectable.get_sprobj()) elif select_type == SELECT_HAIR: @@ -1013,7 +1013,7 @@ init -10 python in mas_selspr: select_map = new_map # change to that map - for item in select_map.itervalues(): + for item in select_map.values(): if use_old or item.selected: prev_hair = moni_chr.hair new_hair = item.selectable.get_sprobj() @@ -1041,7 +1041,7 @@ init -10 python in mas_selspr: select_map = new_map # change to that map - for item in select_map.itervalues(): + for item in select_map.values(): if use_old or item.selected: prev_cloth = moni_chr.clothes new_cloth = item.selectable.get_sprobj() @@ -1224,7 +1224,7 @@ init -10 python in mas_selspr: source - source data to read dest - data place to save """ - for item_name, item in source.iteritems(): + for item_name, item in source.items(): dest[item_name] = item.toTuple() @@ -1251,7 +1251,7 @@ init -10 python in mas_selspr: source - source data to load from dest - data to save the loaded data into """ - for item_name, item_tuple in source.iteritems(): + for item_name, item_tuple in source.items(): if item_name in dest: dest[item_name].fromTuple(item_tuple) @@ -2484,7 +2484,7 @@ init -1 python: if not self.multi_select: # must clean select map - for item in self.select_map.itervalues(): + for item in self.select_map.values(): # setting to False will queue for removal of item # NOTE: the caller must handle teh removal item.selected = False diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index df0b153da8..418e1be9d1 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -1268,7 +1268,7 @@ init 189 python in mas_sprites_json: allow_none = not required is_bad = False - for param_name, verifier_info in param_dict.iteritems(): + for param_name, verifier_info in param_dict.items(): if param_name in jobj: param_val = jobj.pop(param_name) desired_type, verifier = verifier_info @@ -1698,7 +1698,7 @@ init 189 python in mas_sprites_json: hair_map = obj_based.pop("hair_map") is_bad = False - for hair_key,hair_value in hair_map.iteritems(): + for hair_key,hair_value in hair_map.items(): # start with type validations # key @@ -1840,7 +1840,7 @@ init 189 python in mas_sprites_json: ex_props = obj_based.pop("ex_props") isbad = False - for ep_key,ep_val in ex_props.iteritems(): + for ep_key,ep_val in ex_props.items(): if not _verify_str(ep_key): msg_log.append(( MSG_ERR_T, diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index aaba04ec8a..cd014ca211 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -11,7 +11,7 @@ init -989 python: store.mas_submod_utils.writeLog( "\nINSTALLED SUBMODS:\n{0}".format( ",\n".join( - [" '{0}' v{1}".format(submod.name, submod.version) for submod in store.mas_submod_utils.submod_map.itervalues()] + [" '{0}' v{1}".format(submod.name, submod.version) for submod in store.mas_submod_utils.submod_map.values()] ) ) ) @@ -226,7 +226,7 @@ init -991 python in mas_submod_utils: Checks if submods have updated and sets the appropriate update scripts for them to run """ #Iter thru all submods we've got stored - for submod in submod_map.itervalues(): + for submod in submod_map.values(): #If it has updated, we need to call their update scripts and adjust the version data value if submod.hasUpdated(): submod.updateFrom( @@ -259,8 +259,8 @@ init -991 python in mas_submod_utils: """ return map(int, version.split('.')) - for submod in submod_map.itervalues(): - for dependency, minmax_version_tuple in submod.dependencies.iteritems(): + for submod in submod_map.values(): + for dependency, minmax_version_tuple in submod.dependencies.items(): dependency_submod = Submod._getSubmod(dependency) if dependency_submod is not None: @@ -560,7 +560,7 @@ init -980 python in mas_submod_utils: #First, we need to convert the functions into a list of tuples func_list = [ (_function, data_tuple) - for _function, data_tuple in function_plugins[_label].iteritems() + for _function, data_tuple in function_plugins[_label].items() ] return sorted(func_list, key=PRIORITY_SORT_KEY) @@ -613,5 +613,5 @@ init 999 python: Populates a lookup dict for all label overrides which are in effect """ #Let's loop here to update our label overrides map - for overridden_label, label_override in config.label_overrides.iteritems(): + for overridden_label, label_override in config.label_overrides.items(): _OVERRIDE_LABEL_TO_BASE_LABEL_MAP[label_override] = overridden_label diff --git a/Monika After Story/game/zz_weather.rpy b/Monika After Story/game/zz_weather.rpy index 8384ebf858..1f121a1e6b 100644 --- a/Monika After Story/game/zz_weather.rpy +++ b/Monika After Story/game/zz_weather.rpy @@ -341,7 +341,7 @@ init -20 python in mas_weather: if store.persistent._mas_weather_MWdata is None: return - for mw_id, mw_data in store.persistent._mas_weather_MWdata.iteritems(): + for mw_id, mw_data in store.persistent._mas_weather_MWdata.items(): mw_obj = WEATHER_MAP.get(mw_id, None) if mw_obj is not None: mw_obj.fromTuple(mw_data) @@ -351,7 +351,7 @@ init -20 python in mas_weather: """ Saves MASWeather data from weather map into persistent """ - for mw_id, mw_obj in WEATHER_MAP.iteritems(): + for mw_id, mw_obj in WEATHER_MAP.items(): store.persistent._mas_weather_MWdata[mw_id] = mw_obj.toTuple() @@ -360,7 +360,7 @@ init -20 python in mas_weather: Returns number of unlocked weather items """ count = 0 - for mw_id, mw_obj in WEATHER_MAP.iteritems(): + for mw_id, mw_obj in WEATHER_MAP.items(): if mw_obj.unlocked: count += 1 @@ -806,7 +806,7 @@ init -10 python: ignored. Values should be MASWeatherMap objects. """ # validate MASWeatherMap objects - for wmap in filter_pairs.itervalues(): + for wmap in filter_pairs.values(): if not isinstance(wmap, MASWeatherMap): raise TypeError( "Expected MASWeatherMap object, not {0}".format( @@ -1162,7 +1162,7 @@ label monika_change_weather: # build other weather list other_weathers = [ (mw_obj.prompt, mw_obj, False, False) - for mw_id, mw_obj in mas_weather.WEATHER_MAP.iteritems() + for mw_id, mw_obj in mas_weather.WEATHER_MAP.items() if mw_id != "def" and mw_obj.unlocked ] diff --git a/Monika After Story/game/zz_windowreacts.rpy b/Monika After Story/game/zz_windowreacts.rpy index b7d5555466..8c2df61f2e 100644 --- a/Monika After Story/game/zz_windowreacts.rpy +++ b/Monika After Story/game/zz_windowreacts.rpy @@ -253,7 +253,7 @@ init python: if not persistent._mas_windowreacts_windowreacts_enabled or not store.mas_windowreacts.can_show_notifs: return - for ev_label, ev in mas_windowreacts.windowreact_db.iteritems(): + for ev_label, ev in mas_windowreacts.windowreact_db.items(): if ( Event._filterEvent(ev, unlocked=True, aff=store.mas_curr_affection) and mas_isInActiveWindow(ev.category, "non inclusive" in ev.rules) @@ -280,7 +280,7 @@ init python: IN: List of ev_labels to exclude from being unlocked """ - for ev_label, ev in mas_windowreacts.windowreact_db.iteritems(): + for ev_label, ev in mas_windowreacts.windowreact_db.items(): if ev_label not in excluded: ev.unlocked=True diff --git a/testcases/Backgrounds/game/dev/test_bwswap.rpy b/testcases/Backgrounds/game/dev/test_bwswap.rpy index e2d5ebf25e..3d418ed23a 100644 --- a/testcases/Backgrounds/game/dev/test_bwswap.rpy +++ b/testcases/Backgrounds/game/dev/test_bwswap.rpy @@ -33,7 +33,7 @@ label dev_bgsel_loop: # build other backgrounds list other_backgrounds = [ (mbg_obj.prompt, mbg_obj, False, False) - for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.iteritems() + for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.items() if mbg_id != "spaceroom" ] @@ -112,4 +112,4 @@ image test_bgroom_snow = "mod_assets/location/test/test-snow.png" image test_bgroom_def_night = "mod_assets/location/test/test-def-n.png" image test_bgroom_overcast_night = "mod_assets/location/test/test-overcast-n.png" image test_bgroom_rain_night = "mod_assets/location/test/test-rain-n.png" -image test_bgroom_snow_night = "mod_assets/location/test/test-snow-n.png" \ No newline at end of file +image test_bgroom_snow_night = "mod_assets/location/test/test-snow-n.png" From 185447686c9d3271a344a15c7da854c907b3b533 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 28 Dec 2020 17:51:47 -0500 Subject: [PATCH 018/180] add build test for r7.4 branch --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index af1858e545..2a77c162fb 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -6,7 +6,7 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master, pre-master, content, unstable ] + branches: [ master, pre-master, content, unstable, r7.4 ] pull_request: branches: [ content ] From 26aa60ca963bbb7b3642b0dec0fc0fc5d9d3fd62 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 28 Dec 2020 18:11:50 -0500 Subject: [PATCH 019/180] rpy python 3 --- .../game/dev/dev_active_window_check.rpy | 3 ++- .../game/dev/dev_affection-test.rpy | 1 + .../game/dev/dev_backgrounds.rpy | 15 ++++++--------- Monika After Story/game/dev/dev_bday.rpy | 1 + Monika After Story/game/dev/dev_calendar.rpy | 5 +---- .../dev/dev_check_scrollable_menu_test.rpy | 3 +-- .../game/dev/dev_d25_resets.rpy | 3 +-- Monika After Story/game/dev/dev_deco.rpy | 1 + .../game/dev/dev_ds_testing.rpy | 1 + .../game/dev/dev_exp_previewer.rpy | 1 + Monika After Story/game/dev/dev_farewells.rpy | 1 + Monika After Story/game/dev/dev_greetings.rpy | 1 + Monika After Story/game/dev/dev_idle_test.rpy | 4 +--- .../game/dev/dev_in_active_window_check.rpy | 3 ++- .../game/dev/dev_kissing_test.rpy | 1 + Monika After Story/game/dev/dev_moods.rpy | 1 + .../game/dev/dev_mouse_tracker.rpy | 9 +++++---- Monika After Story/game/dev/dev_overlays.rpy | 1 + Monika After Story/game/dev/dev_pg_topics.rpy | 1 + Monika After Story/game/dev/dev_pong.rpy | 1 + Monika After Story/game/dev/dev_python.rpy | 1 + Monika After Story/game/dev/dev_selector.rpy | 11 ++++++----- Monika After Story/game/dev/dev_sprites.rpy | 3 ++- Monika After Story/game/dev/dev_tools.rpy | 5 +++-- Monika After Story/game/dev/dev_unittest.rpy | 19 ++++++++++--------- Monika After Story/game/dev/dev_weather.rpy | 1 + Monika After Story/game/dev/dev_xp.rpy | 1 + .../game/dev/dev_zoom_transition.rpy | 1 + Monika After Story/game/dev/zz_dump.rpy | 2 +- Monika After Story/game/progression.rpy | 1 + Monika After Story/game/screens.rpy | 1 + .../game/script-anniversary.rpy | 1 + Monika After Story/game/script-apologies.rpy | 1 + Monika After Story/game/script-brbs.rpy | 1 + .../game/script-compliments.rpy | 1 + .../game/script-easter-eggs.rpy | 1 + Monika After Story/game/script-farewells.rpy | 1 + Monika After Story/game/script-fun-facts.rpy | 1 + Monika After Story/game/script-grammar.rpy | 1 + Monika After Story/game/script-greetings.rpy | 1 + .../game/script-introduction.rpy | 1 + .../game/script-islands-event.rpy | 1 + Monika After Story/game/script-moods.rpy | 1 + Monika After Story/game/script-python.rpy | 1 + Monika After Story/game/script-songs.rpy | 1 + Monika After Story/game/shake.rpy | 1 + Monika After Story/game/special-effects.rpy | 1 + Monika After Story/game/splash.rpy | 1 + Monika After Story/game/sprite-decoder.rpy | 1 + Monika After Story/game/sprite-generator.rpy | 1 + Monika After Story/game/styles.rpy | 1 + Monika After Story/game/zz_consumables.rpy | 1 + Monika After Story/game/zz_dm.rpy | 1 + Monika After Story/game/zz_dump.rpy | 1 + Monika After Story/game/zz_extrasmenu.rpy | 1 + Monika After Story/game/zz_games.rpy | 1 + Monika After Story/game/zz_graphicsmenu.rpy | 1 + Monika After Story/game/zz_hangman.rpy | 1 + Monika After Story/game/zz_history.rpy | 1 + Monika After Story/game/zz_hotkey_buttons.rpy | 1 + Monika After Story/game/zz_hotkeys.rpy | 1 + Monika After Story/game/zz_interactions.rpy | 1 + Monika After Story/game/zz_monikamovie.rpy | 1 + Monika After Story/game/zz_music_selector.rpy | 1 + Monika After Story/game/zz_overlays.rpy | 1 + Monika After Story/game/zz_pianokeys.rpy | 1 + Monika After Story/game/zz_poemgame.rpy | 1 + Monika After Story/game/zz_poems.rpy | 1 + Monika After Story/game/zz_seasons.rpy | 1 + Monika After Story/game/zz_shields.rpy | 1 + Monika After Story/game/zz_spriteobjects.rpy | 1 + Monika After Story/game/zz_submods.rpy | 1 + Monika After Story/game/zz_threading.rpy | 2 +- Monika After Story/game/zz_transforms.rpy | 1 + Monika After Story/game/zz_weather.rpy | 1 + 75 files changed, 103 insertions(+), 45 deletions(-) diff --git a/Monika After Story/game/dev/dev_active_window_check.rpy b/Monika After Story/game/dev/dev_active_window_check.rpy index 968246a270..73be0409bf 100644 --- a/Monika After Story/game/dev/dev_active_window_check.rpy +++ b/Monika After Story/game/dev/dev_active_window_check.rpy @@ -1,3 +1,4 @@ +rpy python 3 init 5 python: addEvent( Event( @@ -25,4 +26,4 @@ label monika_check_window: m 3eua "[active_wind]." else: m 1hksdlb "[player], you don't have an active window!" - return \ No newline at end of file + return diff --git a/Monika After Story/game/dev/dev_affection-test.rpy b/Monika After Story/game/dev/dev_affection-test.rpy index 96fcda064d..10bec01c44 100644 --- a/Monika After Story/game/dev/dev_affection-test.rpy +++ b/Monika After Story/game/dev/dev_affection-test.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Affection related checks default persistent._mas_disable_sorry = None diff --git a/Monika After Story/game/dev/dev_backgrounds.rpy b/Monika After Story/game/dev/dev_backgrounds.rpy index 68a5c20a6e..ea22014511 100644 --- a/Monika After Story/game/dev/dev_backgrounds.rpy +++ b/Monika After Story/game/dev/dev_backgrounds.rpy @@ -1,3 +1,4 @@ +rpy python 3 # test module for backgrounds init 100 python: @@ -17,7 +18,7 @@ init 100 python: mx_max ): """ - Generates a MASBackgroundFilterManager using sample number of slice + Generates a MASBackgroundFilterManager using sample number of slice sizes. NOTE: verify is NOT called @@ -138,7 +139,7 @@ init 100 python: mx_min - min maxlength time to use in seconds mx_max - max maxlength time ot use in seconds - RETURNS: list of created slices. + RETURNS: list of created slices. """ flt_str = flt_pfx + "_{0}" @@ -162,7 +163,7 @@ init 100 python: return slices - + def _mas_build_random_fake_slice( flt, ml_min, @@ -325,7 +326,7 @@ init 100 python: )) c_off = new_off - else: + else: abc._mn_sr._expand_once(inc_amts) logout.write(str(abc)) @@ -348,7 +349,7 @@ init 100 python: )) c_off = new_off - else: + else: abc._mn_sr._expand_once(inc_amts) logout.write(str(abc)) @@ -391,7 +392,3 @@ init 100 python: logout.write("\n\n\n") logout.write(str(abc)) logout.flush() - - - - diff --git a/Monika After Story/game/dev/dev_bday.rpy b/Monika After Story/game/dev/dev_bday.rpy index 00b3f9734e..0dd61e15a2 100644 --- a/Monika After Story/game/dev/dev_bday.rpy +++ b/Monika After Story/game/dev/dev_bday.rpy @@ -1,3 +1,4 @@ +rpy python 3 # test bday art init 5 python: diff --git a/Monika After Story/game/dev/dev_calendar.rpy b/Monika After Story/game/dev/dev_calendar.rpy index 3c6896e8ab..47808591a1 100644 --- a/Monika After Story/game/dev/dev_calendar.rpy +++ b/Monika After Story/game/dev/dev_calendar.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## calendar testing init 5 python: @@ -32,7 +33,3 @@ label dev_calendar_testing: m "You selected [sel_date_formal]." return - - - - diff --git a/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy b/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy index 1fa00694a3..fab1f3aed9 100644 --- a/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy +++ b/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy @@ -1,4 +1,4 @@ - +rpy python 3 init 5 python: addEvent( Event( @@ -141,4 +141,3 @@ label dev_check_scrollable_menu_sample: m "sample complete" return - diff --git a/Monika After Story/game/dev/dev_d25_resets.rpy b/Monika After Story/game/dev/dev_d25_resets.rpy index 76374a0fd1..1fff829059 100644 --- a/Monika After Story/game/dev/dev_d25_resets.rpy +++ b/Monika After Story/game/dev/dev_d25_resets.rpy @@ -1,3 +1,4 @@ +rpy python 3 # just resetting the d25 events init 998 python: @@ -44,5 +45,3 @@ init 998 python: "mas_nye_monika_nye", "mas_nye_monika_nyd" ) - - diff --git a/Monika After Story/game/dev/dev_deco.rpy b/Monika After Story/game/dev/dev_deco.rpy index acdf6fec4b..085b1808a9 100644 --- a/Monika After Story/game/dev/dev_deco.rpy +++ b/Monika After Story/game/dev/dev_deco.rpy @@ -1,3 +1,4 @@ +rpy python 3 # deco testing diff --git a/Monika After Story/game/dev/dev_ds_testing.rpy b/Monika After Story/game/dev/dev_ds_testing.rpy index 539661d9df..1c7a73ffb8 100644 --- a/Monika After Story/game/dev/dev_ds_testing.rpy +++ b/Monika After Story/game/dev/dev_ds_testing.rpy @@ -1,3 +1,4 @@ +rpy python 3 init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_exp_previewer.rpy b/Monika After Story/game/dev/dev_exp_previewer.rpy index f43a131524..d361fc3974 100644 --- a/Monika After Story/game/dev/dev_exp_previewer.rpy +++ b/Monika After Story/game/dev/dev_exp_previewer.rpy @@ -1,3 +1,4 @@ +rpy python 3 # special module that contains a screen dedicated to expression prevewing. # we really needed this lol. diff --git a/Monika After Story/game/dev/dev_farewells.rpy b/Monika After Story/game/dev/dev_farewells.rpy index 5fb9175fed..2d3e116905 100644 --- a/Monika After Story/game/dev/dev_farewells.rpy +++ b/Monika After Story/game/dev/dev_farewells.rpy @@ -1,3 +1,4 @@ +rpy python 3 # dev related farewells init python: diff --git a/Monika After Story/game/dev/dev_greetings.rpy b/Monika After Story/game/dev/dev_greetings.rpy index cf6de399ba..1b27b2c5a5 100644 --- a/Monika After Story/game/dev/dev_greetings.rpy +++ b/Monika After Story/game/dev/dev_greetings.rpy @@ -1,3 +1,4 @@ +rpy python 3 # dev related greetings # TODO Delete this *Insert Monika with a handgun* diff --git a/Monika After Story/game/dev/dev_idle_test.rpy b/Monika After Story/game/dev/dev_idle_test.rpy index b61153aa4b..f64b8d3824 100644 --- a/Monika After Story/game/dev/dev_idle_test.rpy +++ b/Monika After Story/game/dev/dev_idle_test.rpy @@ -1,3 +1,4 @@ +rpy python 3 # testing module ofr idle init 5 python: @@ -30,6 +31,3 @@ label dev_idle_test: label dev_idle_test_cb: m 1hua "done with idle!" return - - - diff --git a/Monika After Story/game/dev/dev_in_active_window_check.rpy b/Monika After Story/game/dev/dev_in_active_window_check.rpy index 0043873b82..7a4f890438 100644 --- a/Monika After Story/game/dev/dev_in_active_window_check.rpy +++ b/Monika After Story/game/dev/dev_in_active_window_check.rpy @@ -1,3 +1,4 @@ +rpy python 3 init 5 python: addEvent( Event( @@ -33,4 +34,4 @@ label monika_inActiveWindowCheck: $ ActiveWindow = mas_getActiveWindow(True) $ inActiveWindow = mas_isInActiveWindow(active_window_keys, non_inclusive) m 1hua "Okay, your active window was: [ActiveWindow], and your keys returned [inActiveWindow]." - return \ No newline at end of file + return diff --git a/Monika After Story/game/dev/dev_kissing_test.rpy b/Monika After Story/game/dev/dev_kissing_test.rpy index 8427c9f8b5..5498d2bdb5 100644 --- a/Monika After Story/game/dev/dev_kissing_test.rpy +++ b/Monika After Story/game/dev/dev_kissing_test.rpy @@ -1,3 +1,4 @@ +rpy python 3 # test kiss transition init 5 python: diff --git a/Monika After Story/game/dev/dev_moods.rpy b/Monika After Story/game/dev/dev_moods.rpy index b355f07992..c56c1c7d71 100644 --- a/Monika After Story/game/dev/dev_moods.rpy +++ b/Monika After Story/game/dev/dev_moods.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## dev mode eggs #dev mode easter eggs diff --git a/Monika After Story/game/dev/dev_mouse_tracker.rpy b/Monika After Story/game/dev/dev_mouse_tracker.rpy index 3bd3979b18..128718f949 100644 --- a/Monika After Story/game/dev/dev_mouse_tracker.rpy +++ b/Monika After Story/game/dev/dev_mouse_tracker.rpy @@ -1,3 +1,4 @@ +rpy python 3 # module that adds a tiny mouse tracker overlay init -1 python: @@ -242,17 +243,17 @@ init python: self.cz_man.set_disabled(self.mib.ZONE_CHEST_1_L, True) self.quick_add(self.mib.ZONE_HEAD) - + self.quick_add(self.mib.ZONE_NOSE) def build_zone_actions(self): return { self.mib.ZONE_CHEST: MASZoomableInteractable.ZONE_ACTION_NONE, - self.mib.ZONE_CHEST_1_R: + self.mib.ZONE_CHEST_1_R: MASZoomableInteractable.ZONE_ACTION_NONE, - self.mib.ZONE_CHEST_1_M: + self.mib.ZONE_CHEST_1_M: MASZoomableInteractable.ZONE_ACTION_NONE, - self.mib.ZONE_CHEST_1_L: + self.mib.ZONE_CHEST_1_L: MASZoomableInteractable.ZONE_ACTION_NONE, self.mib.ZONE_HEAD: MASZoomableInteractable.ZONE_ACTION_NONE, self.mib.ZONE_NOSE: MASZoomableInteractable.ZONE_ACTION_NONE, diff --git a/Monika After Story/game/dev/dev_overlays.rpy b/Monika After Story/game/dev/dev_overlays.rpy index 6f47f0e5cf..ef68f1c4b7 100644 --- a/Monika After Story/game/dev/dev_overlays.rpy +++ b/Monika After Story/game/dev/dev_overlays.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## overlay testing init 5 python: diff --git a/Monika After Story/game/dev/dev_pg_topics.rpy b/Monika After Story/game/dev/dev_pg_topics.rpy index 36df366903..8294d81a0a 100644 --- a/Monika After Story/game/dev/dev_pg_topics.rpy +++ b/Monika After Story/game/dev/dev_pg_topics.rpy @@ -1,3 +1,4 @@ +rpy python 3 # This file sets up special topics for testing the mas poem minigame # configuration diff --git a/Monika After Story/game/dev/dev_pong.rpy b/Monika After Story/game/dev/dev_pong.rpy index 553afd8333..6100732fed 100644 --- a/Monika After Story/game/dev/dev_pong.rpy +++ b/Monika After Story/game/dev/dev_pong.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## here we run some test cases init 999 python: diff --git a/Monika After Story/game/dev/dev_python.rpy b/Monika After Story/game/dev/dev_python.rpy index ca1956ae5c..59f6ca0d17 100644 --- a/Monika After Story/game/dev/dev_python.rpy +++ b/Monika After Story/game/dev/dev_python.rpy @@ -1,3 +1,4 @@ +rpy python 3 # module for ptod related stuff (dev only init 10 python: diff --git a/Monika After Story/game/dev/dev_selector.rpy b/Monika After Story/game/dev/dev_selector.rpy index c8c229a4be..2f7dee8653 100644 --- a/Monika After Story/game/dev/dev_selector.rpy +++ b/Monika After Story/game/dev/dev_selector.rpy @@ -1,7 +1,8 @@ +rpy python 3 # selector testing init -100 python: - + def dev_mas_unlock_all_sprites(): for sel_obj in store.mas_selspr.ACS_SEL_SL: sel_obj.unlocked = True @@ -113,7 +114,7 @@ label dev_selector_test: return # TODO: this needs to be called, so we need to redo the jump logic., -# jump mas_selector_sidebar_select(start_test_items, "dev_selector_test_confirm", "dev_selector_test_cancel", +# jump mas_selector_sidebar_select(start_test_items, "dev_selector_test_confirm", "dev_selector_test_cancel", label dev_selector_test_confirm: hide screen mas_selector_sidebar @@ -161,7 +162,7 @@ label dev_selector_hair_test: call mas_selector_sidebar_select_hair(sorted_hair, mailbox=mailbox, select_map=sel_map) - # undo the unlocks + # undo the unlocks python: for item in sorted_hair: item.unlocked = unlock_map[item.name] @@ -201,7 +202,7 @@ label dev_selector_clothes_test: call mas_selector_sidebar_select_clothes(sorted_clothes, mailbox=mailbox, select_map=sel_map) - # undo the unlocks + # undo the unlocks python: for item in sorted_clothes: item.unlocked = unlock_map[item.name] @@ -248,7 +249,7 @@ label dev_selector_acs_ribbons_test: call mas_selector_sidebar_select_acs(use_acs, mailbox=mailbox, select_map=sel_map) - # undo the unlocks + # undo the unlocks python: for item in use_acs: item.unlocked = unlock_map[item.name] diff --git a/Monika After Story/game/dev/dev_sprites.rpy b/Monika After Story/game/dev/dev_sprites.rpy index 14e68f3acc..a0cb548c41 100644 --- a/Monika After Story/game/dev/dev_sprites.rpy +++ b/Monika After Story/game/dev/dev_sprites.rpy @@ -1,3 +1,4 @@ +rpy python 3 # sprite testing code init 100 python: @@ -229,7 +230,7 @@ label dev_sp_obj_pp_test: m "but side effects may occur." label dev_sp_obj_pp_test_top: - + call screen mas_gen_scrollable_menu(top_level_menu, store.mas_moods.MOOD_AREA, store.mas_moods.MOOD_XALIGN, returner) if _return is False: diff --git a/Monika After Story/game/dev/dev_tools.rpy b/Monika After Story/game/dev/dev_tools.rpy index 83d321e751..b9ab0718f9 100644 --- a/Monika After Story/game/dev/dev_tools.rpy +++ b/Monika After Story/game/dev/dev_tools.rpy @@ -1,7 +1,8 @@ +rpy python 3 # basic dev tool stuff init 800 python: - + def mas_remove_event(*labels): """ Removes an event from the persistent database and lock DB @@ -23,7 +24,7 @@ init 800 python: if label in Event.INIT_LOCKDB: Event.INIT_LOCKDB.pop(label) - + persistent.closed_self = True persistent._mas_game_crashed = False renpy.save_persistent() diff --git a/Monika After Story/game/dev/dev_unittest.rpy b/Monika After Story/game/dev/dev_unittest.rpy index 6b48be7be6..d6f3c65814 100644 --- a/Monika After Story/game/dev/dev_unittest.rpy +++ b/Monika After Story/game/dev/dev_unittest.rpy @@ -1,3 +1,4 @@ +rpy python 3 # unit testing module # NOTE: no framework for now @@ -105,10 +106,10 @@ init -1 python in mas_dev_unit_tests: )) return False - # now check keys + values + # now check keys + values a_keys = sorted(actual.keys()) for e_key in expected: - + if e_key not in a_keys: self.tests.append(MASUnitTest( self.test_name, @@ -119,12 +120,12 @@ init -1 python in mas_dev_unit_tests: )) return False - # pop key off + # pop key off a_keys.remove(e_key) if not self.assertEqual(expected[e_key], actual[e_key]): return False - # if any keys remain in a, then we had a mismatch + # if any keys remain in a, then we had a mismatch if len(a_keys) > 0: self.tests.append(MASUnitTest( self.test_name, @@ -359,7 +360,7 @@ label dev_unit_tests_show_pass: label dev_unit_tests_show_fail: m 1ektsc "!!!FAILED!!!" - return + return label dev_unit_tests_show_msgs(msg_list, format_text=False): $ index = 0 @@ -474,7 +475,7 @@ label dev_unit_test_event_yearadjust: end_dt = now_dt - datetime.timedelta(days=380) expected = (add_years(start_dt, 2), add_years(end_dt, 2), True) actual = Event._yearAdjust(start_dt, end_dt, []) - eya_tester.assertEqual(expected, actual) + eya_tester.assertEqual(expected, actual) eya_tester.prepareTest("ahead now, same year") now_dt = datetime.datetime.now() @@ -482,7 +483,7 @@ label dev_unit_test_event_yearadjust: end_dt = now_dt + datetime.timedelta(days=10) expected = (start_dt, end_dt, False) actual = Event._yearAdjust(start_dt, end_dt, []) - eya_tester.assertEqual(expected, actual) + eya_tester.assertEqual(expected, actual) eya_tester.prepareTest("ahead now, diff year") now_dt = datetime.datetime.now() @@ -517,7 +518,7 @@ label dev_unit_test_json_masposemap: return gen_data(MASPoseArms.J_NAME_LEFT, ldata) def gen_right(rdata): - return gen_data(MASPoseArms.J_NAME_RIGHT, rdata) + return gen_data(MASPoseArms.J_NAME_RIGHT, rdata) def arms_both(extra=False): data = gen_both(("both_pa", True, True)) @@ -1756,7 +1757,7 @@ label dev_unit_test_mhs: mhs_tester.assertEqual(test_data[0], test_mhs.trigger) mhs_tester.assertFalse(test_mhs.use_year_before) store.mas_globals.tt_detected = prev_data[0] - MASHistorySaver.first_sesh = prev_data[1] + MASHistorySaver.first_sesh = prev_data[1] mhs_tester.prepareTest("isActive|continuous") test_mhs = gen_fresh_mhs() diff --git a/Monika After Story/game/dev/dev_weather.rpy b/Monika After Story/game/dev/dev_weather.rpy index 724b52163f..2abed3c614 100644 --- a/Monika After Story/game/dev/dev_weather.rpy +++ b/Monika After Story/game/dev/dev_weather.rpy @@ -1,3 +1,4 @@ +rpy python 3 # testin gweather init 5 python: # available only if moni affection is normal+ diff --git a/Monika After Story/game/dev/dev_xp.rpy b/Monika After Story/game/dev/dev_xp.rpy index aec25fe781..c7304ff4d2 100644 --- a/Monika After Story/game/dev/dev_xp.rpy +++ b/Monika After Story/game/dev/dev_xp.rpy @@ -1,3 +1,4 @@ +rpy python 3 init 5 python: addEvent( diff --git a/Monika After Story/game/dev/dev_zoom_transition.rpy b/Monika After Story/game/dev/dev_zoom_transition.rpy index c5fe6d2399..d43d8d298b 100644 --- a/Monika After Story/game/dev/dev_zoom_transition.rpy +++ b/Monika After Story/game/dev/dev_zoom_transition.rpy @@ -1,3 +1,4 @@ +rpy python 3 # test zoom transitions init 5 python: addEvent( diff --git a/Monika After Story/game/dev/zz_dump.rpy b/Monika After Story/game/dev/zz_dump.rpy index a349179ce6..565505fe34 100644 --- a/Monika After Story/game/dev/zz_dump.rpy +++ b/Monika After Story/game/dev/zz_dump.rpy @@ -1,3 +1,4 @@ +rpy python 3 # emergency dumping file init 999 python: @@ -15,4 +16,3 @@ init 999 python: # ) # ) # del outtext - diff --git a/Monika After Story/game/progression.rpy b/Monika After Story/game/progression.rpy index 1bd5141910..a691f29d10 100644 --- a/Monika After Story/game/progression.rpy +++ b/Monika After Story/game/progression.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that defines functions for handling game progression and leveling up diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 9cb098bb98..e7a27812d8 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -1,3 +1,4 @@ +rpy python 3 init 100 python: layout.QUIT = store.mas_layout.QUIT layout.UNSTABLE = store.mas_layout.UNSTABLE diff --git a/Monika After Story/game/script-anniversary.rpy b/Monika After Story/game/script-anniversary.rpy index 90fb650f8c..05e996f68e 100644 --- a/Monika After Story/game/script-anniversary.rpy +++ b/Monika After Story/game/script-anniversary.rpy @@ -1,3 +1,4 @@ +rpy python 3 init -2 python in mas_anni: #needed to lower this in order to get isAnni() working for special day usage import store.evhand as evhand import store.mas_calendar as mas_cal diff --git a/Monika After Story/game/script-apologies.rpy b/Monika After Story/game/script-apologies.rpy index 525436bb2e..a10d703777 100644 --- a/Monika After Story/game/script-apologies.rpy +++ b/Monika After Story/game/script-apologies.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Create an apology db for storing our times #Stores the event label as a key, its corresponding data is a tuple where: # [0] -> timedelta defined by: current total playtime + apology_active_expiry time diff --git a/Monika After Story/game/script-brbs.rpy b/Monika After Story/game/script-brbs.rpy index 9c5345b58b..6f79d4ede8 100644 --- a/Monika After Story/game/script-brbs.rpy +++ b/Monika After Story/game/script-brbs.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## This script file holds all of the brb topics # Some conventions: # - All brbs should have their markSeen set to True so they don't show up in unseen diff --git a/Monika After Story/game/script-compliments.rpy b/Monika After Story/game/script-compliments.rpy index 71758c9e7c..7262e6d03a 100644 --- a/Monika After Story/game/script-compliments.rpy +++ b/Monika After Story/game/script-compliments.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module for complimenting Monika # # Compliments work by using the "unlocked" logic. diff --git a/Monika After Story/game/script-easter-eggs.rpy b/Monika After Story/game/script-easter-eggs.rpy index ec5256a885..5294386d29 100644 --- a/Monika After Story/game/script-easter-eggs.rpy +++ b/Monika After Story/game/script-easter-eggs.rpy @@ -1,3 +1,4 @@ +rpy python 3 # script stuff that is actually easter eggs # sayori music chnage/scare diff --git a/Monika After Story/game/script-farewells.rpy b/Monika After Story/game/script-farewells.rpy index 317d6ce79f..2d55d8b00f 100644 --- a/Monika After Story/game/script-farewells.rpy +++ b/Monika After Story/game/script-farewells.rpy @@ -1,3 +1,4 @@ +rpy python 3 ##This file contains all of the variations of goodbye that monika can give. ## This also contains a store with a utility function to select an appropriate ## farewell diff --git a/Monika After Story/game/script-fun-facts.rpy b/Monika After Story/game/script-fun-facts.rpy index 953baab286..e077f61b27 100644 --- a/Monika After Story/game/script-fun-facts.rpy +++ b/Monika After Story/game/script-fun-facts.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Persistent event database for fun facts default persistent._mas_fun_facts_database = dict() diff --git a/Monika After Story/game/script-grammar.rpy b/Monika After Story/game/script-grammar.rpy index b61c81df63..475e12c8da 100644 --- a/Monika After Story/game/script-grammar.rpy +++ b/Monika After Story/game/script-grammar.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Monika's Grammar Tip of the Day (GTOD) # TIPS # 0 - Intro diff --git a/Monika After Story/game/script-greetings.rpy b/Monika After Story/game/script-greetings.rpy index 648fb260d2..0fe1266172 100644 --- a/Monika After Story/game/script-greetings.rpy +++ b/Monika After Story/game/script-greetings.rpy @@ -1,3 +1,4 @@ +rpy python 3 ##This page holds all of the random greetings that Monika can give you after you've gone through all of her "reload" scripts #Make a list of every label that starts with "greeting_", and use that for random greetings during startup diff --git a/Monika After Story/game/script-introduction.rpy b/Monika After Story/game/script-introduction.rpy index a70926297d..2fc28df80a 100644 --- a/Monika After Story/game/script-introduction.rpy +++ b/Monika After Story/game/script-introduction.rpy @@ -1,3 +1,4 @@ +rpy python 3 init -1 python: import store.mas_affection as mas_aff label introduction: diff --git a/Monika After Story/game/script-islands-event.rpy b/Monika After Story/game/script-islands-event.rpy index 88d9e31167..b9478205b0 100644 --- a/Monika After Story/game/script-islands-event.rpy +++ b/Monika After Story/game/script-islands-event.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Monika's ???? Event # deserves it's own file because of how much dialogue these have # it basically shows a new screen over everything, and has an image map diff --git a/Monika After Story/game/script-moods.rpy b/Monika After Story/game/script-moods.rpy index d09fbb00c5..340092a60c 100644 --- a/Monika After Story/game/script-moods.rpy +++ b/Monika After Story/game/script-moods.rpy @@ -1,3 +1,4 @@ +rpy python 3 # module that handles the mood system # diff --git a/Monika After Story/game/script-python.rpy b/Monika After Story/game/script-python.rpy index d3e13a8c4a..c6cf616b54 100644 --- a/Monika After Story/game/script-python.rpy +++ b/Monika After Story/game/script-python.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Monika's Python Tip of the Day (PTOD) # # I probably will be adding many of these, so For the sake of organization diff --git a/Monika After Story/game/script-songs.rpy b/Monika After Story/game/script-songs.rpy index 5d2d11948e..0d979f10bf 100644 --- a/Monika After Story/game/script-songs.rpy +++ b/Monika After Story/game/script-songs.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Event database for songs default persistent._mas_songs_database = dict() diff --git a/Monika After Story/game/shake.rpy b/Monika After Story/game/shake.rpy index 6ce4035da1..fc43fea1b8 100644 --- a/Monika After Story/game/shake.rpy +++ b/Monika After Story/game/shake.rpy @@ -1,3 +1,4 @@ +rpy python 3 init: python: diff --git a/Monika After Story/game/special-effects.rpy b/Monika After Story/game/special-effects.rpy index e477e07076..c8b0ccc52b 100644 --- a/Monika After Story/game/special-effects.rpy +++ b/Monika After Story/game/special-effects.rpy @@ -1,3 +1,4 @@ +rpy python 3 # This file is meant to store any special effects. # These can be some images or transforms. diff --git a/Monika After Story/game/splash.rpy b/Monika After Story/game/splash.rpy index ea9fbf765d..f93653428f 100644 --- a/Monika After Story/game/splash.rpy +++ b/Monika After Story/game/splash.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## This splash screen is the first thing that Renpy will show the player ## ## Before load, check to be sure that the archive files were found. diff --git a/Monika After Story/game/sprite-decoder.rpy b/Monika After Story/game/sprite-decoder.rpy index 33a9ebae2c..30964317bd 100644 --- a/Monika After Story/game/sprite-decoder.rpy +++ b/Monika After Story/game/sprite-decoder.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Runtime code equivalent of our spritemaker tool init python in mas_sprite_decoder: import store diff --git a/Monika After Story/game/sprite-generator.rpy b/Monika After Story/game/sprite-generator.rpy index bae48aabe8..0b86b2f775 100644 --- a/Monika After Story/game/sprite-generator.rpy +++ b/Monika After Story/game/sprite-generator.rpy @@ -1,3 +1,4 @@ +rpy python 3 init python in mas_sprites: #START: main funcs diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index a2619c701d..58ce719dd5 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -1,3 +1,4 @@ +rpy python 3 # START: vars # Whether dark mode is enabled or not default persistent._mas_dark_mode_enabled = False diff --git a/Monika After Story/game/zz_consumables.rpy b/Monika After Story/game/zz_consumables.rpy index a22ee06228..bbe956e7ea 100644 --- a/Monika After Story/game/zz_consumables.rpy +++ b/Monika After Story/game/zz_consumables.rpy @@ -1,3 +1,4 @@ +rpy python 3 default persistent._mas_current_consumable = { 0: { "prep_time": None, diff --git a/Monika After Story/game/zz_dm.rpy b/Monika After Story/game/zz_dm.rpy index 6cd403c820..6f686eac56 100644 --- a/Monika After Story/game/zz_dm.rpy +++ b/Monika After Story/game/zz_dm.rpy @@ -1,3 +1,4 @@ +rpy python 3 # data migration module init -999 python in _mas_dm_dm: diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy index f9f29eb263..a19b4c0f27 100644 --- a/Monika After Story/game/zz_dump.rpy +++ b/Monika After Story/game/zz_dump.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## dumps file for unstablers init 999 python: diff --git a/Monika After Story/game/zz_extrasmenu.rpy b/Monika After Story/game/zz_extrasmenu.rpy index d574c9a1e2..256abf7c12 100644 --- a/Monika After Story/game/zz_extrasmenu.rpy +++ b/Monika After Story/game/zz_extrasmenu.rpy @@ -1,3 +1,4 @@ +rpy python 3 # module containing what we call interactive modes (extras) # basically things like headpats and other mouse-based interactions should be # defined here diff --git a/Monika After Story/game/zz_games.rpy b/Monika After Story/game/zz_games.rpy index 0087af9e1e..707cc006e5 100644 --- a/Monika After Story/game/zz_games.rpy +++ b/Monika After Story/game/zz_games.rpy @@ -1,3 +1,4 @@ +rpy python 3 default persistent._mas_game_database = dict() init -10 python in mas_games: diff --git a/Monika After Story/game/zz_graphicsmenu.rpy b/Monika After Story/game/zz_graphicsmenu.rpy index 7e5c31f55a..1996c5f165 100644 --- a/Monika After Story/game/zz_graphicsmenu.rpy +++ b/Monika After Story/game/zz_graphicsmenu.rpy @@ -1,3 +1,4 @@ +rpy python 3 # graphics selection menu # we do this instead of the actual one because the real one breaks everything diff --git a/Monika After Story/game/zz_hangman.rpy b/Monika After Story/game/zz_hangman.rpy index e1bfad1102..8c673cfc26 100644 --- a/Monika After Story/game/zz_hangman.rpy +++ b/Monika After Story/game/zz_hangman.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that does hangman man # # DEPENDS ON: diff --git a/Monika After Story/game/zz_history.rpy b/Monika After Story/game/zz_history.rpy index dabbbebaa0..9fd1e93f8c 100644 --- a/Monika After Story/game/zz_history.rpy +++ b/Monika After Story/game/zz_history.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Historical data module # # How this works: diff --git a/Monika After Story/game/zz_hotkey_buttons.rpy b/Monika After Story/game/zz_hotkey_buttons.rpy index 30ddb523e9..6aa2d25cf6 100644 --- a/Monika After Story/game/zz_hotkey_buttons.rpy +++ b/Monika After Story/game/zz_hotkey_buttons.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that handles hotkey button screen # diff --git a/Monika After Story/game/zz_hotkeys.rpy b/Monika After Story/game/zz_hotkeys.rpy index 0513d31a73..9f3507d6c5 100644 --- a/Monika After Story/game/zz_hotkeys.rpy +++ b/Monika After Story/game/zz_hotkeys.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that is just for hotkeys and other keymaps # diff --git a/Monika After Story/game/zz_interactions.rpy b/Monika After Story/game/zz_interactions.rpy index 387cd54b62..771cb39a0e 100644 --- a/Monika After Story/game/zz_interactions.rpy +++ b/Monika After Story/game/zz_interactions.rpy @@ -1,3 +1,4 @@ +rpy python 3 # all complicated interactions go here # mainly: # boop diff --git a/Monika After Story/game/zz_monikamovie.rpy b/Monika After Story/game/zz_monikamovie.rpy index c55b182308..e68f2d8a41 100644 --- a/Monika After Story/game/zz_monikamovie.rpy +++ b/Monika After Story/game/zz_monikamovie.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Watch a movie module # diff --git a/Monika After Story/game/zz_music_selector.rpy b/Monika After Story/game/zz_music_selector.rpy index 0e079631df..4f219b3620 100644 --- a/Monika After Story/game/zz_music_selector.rpy +++ b/Monika After Story/game/zz_music_selector.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that handles the music selection screen # we start with zz to ensure this loads LAST # diff --git a/Monika After Story/game/zz_overlays.rpy b/Monika After Story/game/zz_overlays.rpy index 18fa07054f..1202c2b050 100644 --- a/Monika After Story/game/zz_overlays.rpy +++ b/Monika After Story/game/zz_overlays.rpy @@ -1,3 +1,4 @@ +rpy python 3 # module for handling of overlay screens nicely # NOTE: do not write screen overlays that need to be past 500 init level. # this should be ran last to setup proper linkages diff --git a/Monika After Story/game/zz_pianokeys.rpy b/Monika After Story/game/zz_pianokeys.rpy index b33d3bced9..fb34aa9b0d 100644 --- a/Monika After Story/game/zz_pianokeys.rpy +++ b/Monika After Story/game/zz_pianokeys.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that lets you play the piano # # Adding custom Piano Songs: diff --git a/Monika After Story/game/zz_poemgame.rpy b/Monika After Story/game/zz_poemgame.rpy index 15ef852259..67ac34a4cd 100644 --- a/Monika After Story/game/zz_poemgame.rpy +++ b/Monika After Story/game/zz_poemgame.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that contains a modified version of the poem minigame so we can use # it seamlessly in topics # diff --git a/Monika After Story/game/zz_poems.rpy b/Monika After Story/game/zz_poems.rpy index 59b68b0e5e..6a158b4eda 100644 --- a/Monika After Story/game/zz_poems.rpy +++ b/Monika After Story/game/zz_poems.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Dict holding seen poems and amount of times seen #poem_id:shown_count default persistent._mas_poems_seen = dict() diff --git a/Monika After Story/game/zz_seasons.rpy b/Monika After Story/game/zz_seasons.rpy index 36de03d705..da4c6f251f 100644 --- a/Monika After Story/game/zz_seasons.rpy +++ b/Monika After Story/game/zz_seasons.rpy @@ -1,3 +1,4 @@ +rpy python 3 ## seasonal module. # contains season functions and seasonal programming points. diff --git a/Monika After Story/game/zz_shields.rpy b/Monika After Story/game/zz_shields.rpy index 853740c86f..f221f27066 100644 --- a/Monika After Story/game/zz_shields.rpy +++ b/Monika After Story/game/zz_shields.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module that contains both work-flow specific shield functions and # generalized shield functions. # diff --git a/Monika After Story/game/zz_spriteobjects.rpy b/Monika After Story/game/zz_spriteobjects.rpy index 175bf04833..40b5742ee3 100644 --- a/Monika After Story/game/zz_spriteobjects.rpy +++ b/Monika After Story/game/zz_spriteobjects.rpy @@ -1,3 +1,4 @@ +rpy python 3 # All Sprite objects belong here # # For documentation on classes, see sprite-chart diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index cd014ca211..5969ed5e85 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -1,3 +1,4 @@ +rpy python 3 init -999: default persistent._mas_submod_version_data = dict() diff --git a/Monika After Story/game/zz_threading.rpy b/Monika After Story/game/zz_threading.rpy index 1f199b2387..a9da6cb935 100644 --- a/Monika After Story/game/zz_threading.rpy +++ b/Monika After Story/game/zz_threading.rpy @@ -1,4 +1,4 @@ - +rpy python 3 init -750 python in mas_threading: # threading related vars import threading diff --git a/Monika After Story/game/zz_transforms.rpy b/Monika After Story/game/zz_transforms.rpy index 2945a4b80a..f94bc9324b 100644 --- a/Monika After Story/game/zz_transforms.rpy +++ b/Monika After Story/game/zz_transforms.rpy @@ -1,3 +1,4 @@ +rpy python 3 # Module containing custom transform functions. # Last just because # NOTE: Depends on script-poemgame diff --git a/Monika After Story/game/zz_weather.rpy b/Monika After Story/game/zz_weather.rpy index 1f121a1e6b..cd34b1c220 100644 --- a/Monika After Story/game/zz_weather.rpy +++ b/Monika After Story/game/zz_weather.rpy @@ -1,3 +1,4 @@ +rpy python 3 #Stores the last weather the player had chosen #Default: "auto" default persistent._mas_current_weather = "auto" From 217d71b57414ff16471c7d854139d9fda3523992 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 28 Dec 2020 18:32:20 -0500 Subject: [PATCH 020/180] fix dict issue --- Monika After Story/game/script-topics.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/script-topics.rpy b/Monika After Story/game/script-topics.rpy index dbe4fd2c37..2a738fe94d 100644 --- a/Monika After Story/game/script-topics.rpy +++ b/Monika After Story/game/script-topics.rpy @@ -666,7 +666,7 @@ init python in mas_bookmarks_derand: #Firstly, let's get our derandom keys derand_keys = [ label_prefix_data["derand_persist_key"] - for label_prefix_data in label_prefix_map() + for label_prefix_data in label_prefix_map.values() if "derand_persist_key" in label_prefix_data ] From 14721d12c81cea9dce8b5e3a1209a8b07e555230 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 28 Dec 2020 18:36:17 -0500 Subject: [PATCH 021/180] better lint --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 2a77c162fb..3b230a7bdf 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -65,7 +65,7 @@ jobs: - name: rpy lint run: | cd renpy - ./renpy.sh "../mas0105/" lint > renpy_output + ./renpy.sh "../mas0105/" lint 2>&1 > tee renpy_output python ../tools/renpy_lint_parser.py cat renpy_output_clean From ba0c7baa21f7bda56ffe95e41efa42008aba9c9a Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 4 Jan 2021 12:24:17 -0500 Subject: [PATCH 022/180] use current nightly instead --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 3b230a7bdf..fbe2a421f1 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,7 +39,7 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget https://www.renpy.org/dl/7.4.0/renpy-7.4.0-sdk.tar.bz2 + wget https://nightly.renpy.org/current/renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 tar xf renpy-7.4.0-sdk.tar.bz2 rm renpy-7.4.0-sdk.tar.bz2 mv renpy-7.4.0-sdk renpy From 9b8be70b5ca30f58981615e809fb8ce835d978aa Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 4 Jan 2021 12:27:00 -0500 Subject: [PATCH 023/180] fix dum issue --- .github/workflows/mas_check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index fbe2a421f1..40febff9fd 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -40,9 +40,9 @@ jobs: - name: Download rpy source run: | wget https://nightly.renpy.org/current/renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 - tar xf renpy-7.4.0-sdk.tar.bz2 - rm renpy-7.4.0-sdk.tar.bz2 - mv renpy-7.4.0-sdk renpy + tar xf renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 + rm renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 + mv renpy-nightly-2021-01-04-a2dd40035-sdk # download base mas - name: Download base MAS + copy files over From fe9a5c31ee6f9f18532b4ad25faac5326e5cf925 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 4 Jan 2021 12:29:08 -0500 Subject: [PATCH 024/180] another dum thing --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 40febff9fd..d62a717a12 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -42,7 +42,7 @@ jobs: wget https://nightly.renpy.org/current/renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 tar xf renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 rm renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 - mv renpy-nightly-2021-01-04-a2dd40035-sdk + mv renpy-nightly-2021-01-04-a2dd40035-sdk renpy # download base mas - name: Download base MAS + copy files over From 7dc35902230156732ab6f45a70490aa707c02b38 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 4 Jan 2021 13:07:24 -0500 Subject: [PATCH 025/180] remove r7 mode things --- Monika After Story/game/definitions.rpy | 2 +- Monika After Story/game/updater.rpy | 7 ++----- Monika After Story/game/zz_dump.rpy | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 0e4960f10c..41a7762275 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -4327,7 +4327,7 @@ init -995 python in mas_utils: return False # unstable should never delete logs - if store.persistent._mas_unstable_mode or store.mas_r7_mode: + if store.persistent._mas_unstable_mode: mas_log = getMASLog("log/mas_log", append=True, flush=True) else: mas_log = getMASLog("log/mas_log") diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index a36a6775b5..2b31029dc4 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -447,7 +447,7 @@ init -1 python: return # old version check - if mas_r7_mode: + if persistent._mas_unstable_mode: # rpartion the ., the last item should be build number lv_build_number = store.mas_utils.tryparseint( latest_version.rpartition(".")[2], @@ -668,10 +668,7 @@ init python in mas_updater: curr_time = time.time() - if store.mas_r7_mode: - update_link = r7 - - elif renpy.game.persistent._mas_unstable_mode: + if renpy.game.persistent._mas_unstable_mode: update_link = unstable else: diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy index a19b4c0f27..2d695755b1 100644 --- a/Monika After Story/game/zz_dump.rpy +++ b/Monika After Story/game/zz_dump.rpy @@ -252,7 +252,8 @@ init 999 python: last_sesh_ed = persistent.sessions.get("last_session_end", "N/A") if total_sesh and total_playtime is not None: - avg_sesh = total_playtime / total_sesh + total_playtime = total_playtime.total_seconds() + avg_sesh = datetime.timedelta(seconds=total_playtime / total_sesh) else: avg_sesh = "N/A" From 4b3e137453bacdc4c96be8911fb1d3ef7ca695fd Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Mar 2021 12:15:56 -0400 Subject: [PATCH 026/180] use current --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index cb80d77ca9..8e393dec3e 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,7 +39,7 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget https://nightly.renpy.org/current/renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 + wget https://www.renpy.org/dl/7.4.4/renpy-7.4.4-sdk.tar.bz2 tar xf renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 rm renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 mv renpy-nightly-2021-01-04-a2dd40035-sdk renpy From ca784c00ef8f5642b833f16a18fdbf13c5bf1940 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Mar 2021 12:18:17 -0400 Subject: [PATCH 027/180] don't mind me --- .github/workflows/mas_check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 8e393dec3e..f46720cd3e 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -40,9 +40,9 @@ jobs: - name: Download rpy source run: | wget https://www.renpy.org/dl/7.4.4/renpy-7.4.4-sdk.tar.bz2 - tar xf renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 + tar xf renpy-7.4.4-sdk.tar.bz2 rm renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 - mv renpy-nightly-2021-01-04-a2dd40035-sdk renpy + mv renpy-7.4.4-sdk renpy # download base mas - name: Download base MAS + copy files over From 31503c938635f31e44c19f1802e7a840681c09d9 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Mar 2021 12:18:57 -0400 Subject: [PATCH 028/180] fucc --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index f46720cd3e..4f164d6a78 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -41,7 +41,7 @@ jobs: run: | wget https://www.renpy.org/dl/7.4.4/renpy-7.4.4-sdk.tar.bz2 tar xf renpy-7.4.4-sdk.tar.bz2 - rm renpy-nightly-2021-01-04-a2dd40035-sdk.tar.bz2 + rm renpy-7.4.4-sdk.tar.bz2 mv renpy-7.4.4-sdk renpy # download base mas From a25b0ae08f20b1f0d914a2a42987f601cceee725 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Mar 2021 14:42:58 -0400 Subject: [PATCH 029/180] trying something stupid here --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 4f164d6a78..5183449f71 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -65,7 +65,7 @@ jobs: - name: rpy lint run: | cd renpy - ./renpy.sh "../mas0105/" lint &> renpy_output + ./renpy.sh "../mas0105/" lint &> renpy_output || true python ../tools/renpy_lint_parser.py cat renpy_output_clean From 48baacb92d395f1f72f146b279d590863fdb8fae Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Mar 2021 15:11:30 -0400 Subject: [PATCH 030/180] I think this should work properly --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 5183449f71..b544963dba 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -65,7 +65,7 @@ jobs: - name: rpy lint run: | cd renpy - ./renpy.sh "../mas0105/" lint &> renpy_output || true + ./renpy.sh "../mas0105/" lint &> >(tee renpy_output) python ../tools/renpy_lint_parser.py cat renpy_output_clean From 540b6e8c8c43bfb5d55b3344ef938eaa1cf078eb Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 30 Aug 2021 23:32:03 -0400 Subject: [PATCH 031/180] update dicts to use py3 looping --- Monika After Story/game/chess.rpy | 6 +++--- Monika After Story/game/definitions.rpy | 2 +- Monika After Story/game/dev/dev_stories.rpy | 2 +- Monika After Story/game/script-stories.rpy | 2 +- Monika After Story/game/sprite-chart.rpy | 13 +++++++------ Monika After Story/game/sprite-decoder.rpy | 2 +- Monika After Story/game/styles.rpy | 4 ++-- Monika After Story/game/updates.rpy | 2 +- Monika After Story/game/zz_pianokeys.rpy | 4 ++-- Monika After Story/game/zz_windowutils.rpy | 4 ++-- 10 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index d5feef09a1..1055a5a834 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -2235,7 +2235,7 @@ init python: self.piece_map = dict() #And refill it - for position, Piece in self.board.piece_map().iteritems(): + for position, Piece in self.board.piece_map().items(): MASPiece.fromPiece( Piece, MASChessDisplayableBase.square_to_board_coords(position), @@ -2494,7 +2494,7 @@ init python: renderer.blit(highlight_yellow, MASChessDisplayableBase.board_coords_to_screen_coords(hl)) #Draw the pieces on the Board renderer. - for piece_location, Piece in self.piece_map.iteritems(): + for piece_location, Piece in self.piece_map.items(): #Unpack the location ix, iy = piece_location @@ -2780,7 +2780,7 @@ init python: IMG_MAP = { color + (symbol.upper() if color == "w" else symbol): Image("mod_assets/games/chess/pieces/{0}{1}.png".format(color, (symbol.upper() if color == "w" else symbol))) - for color in FP_COLOR_LOOKUP.itervalues() + for color in FP_COLOR_LOOKUP.values() for symbol in mas_chess.PIECE_POOL } diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index b9fe1eb3d9..4fe3c42275 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -1082,7 +1082,7 @@ python early: ASSUMES: mas_all_ev_db """ - for ev in mas_all_ev_db.itervalues(): + for ev in mas_all_ev_db.values(): if ev.conditional is not None: try: renpy.python.py_eval_bytecode(cls._conditional_cache[ev.conditional]) diff --git a/Monika After Story/game/dev/dev_stories.rpy b/Monika After Story/game/dev/dev_stories.rpy index d50a2f5931..cf5993460a 100644 --- a/Monika After Story/game/dev/dev_stories.rpy +++ b/Monika After Story/game/dev/dev_stories.rpy @@ -3,5 +3,5 @@ init 20 python in mas_stories: """ Dev function, unlocks all stories """ - for story in story_database.itervalues(): + for story in story_database.values(): story.unlocked=True diff --git a/Monika After Story/game/script-stories.rpy b/Monika After Story/game/script-stories.rpy index c9b48ede13..4c20ee8cd3 100644 --- a/Monika After Story/game/script-stories.rpy +++ b/Monika After Story/game/script-stories.rpy @@ -163,7 +163,7 @@ label monika_short_stories_menu: # build menu list stories_menu_items = [ (story_ev.prompt, story_evl, False, False) - for story_evl, story_ev in mas_stories.story_database.iteritems() + for story_evl, story_ev in mas_stories.story_database.items() if Event._filterEvent( story_ev, pool=False, diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index 8fdd43721e..8d296b83fd 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -1149,7 +1149,7 @@ init -5 python in mas_sprites: if no ACS of the given type """ return [ - acs for acs in ACS_MAP.itervalues() + acs for acs in ACS_MAP.values() if acs.acs_type == acs_type ] @@ -7871,7 +7871,7 @@ python early: exps = (exps,) for exp in exps: - for aff_lvl, exp_list in self.exp_map.iteritems(): + for aff_lvl, exp_list in self.exp_map.items(): if exp.check_aff(aff_lvl): exp_list.append(exp) @@ -7924,7 +7924,7 @@ python early: """ need_redraw = self.current_exp is exp - for exp_list in self.exp_map.itervalues(): + for exp_list in self.exp_map.values(): if exp in exp_list: exp_list.remove(exp) need_redraw = True @@ -7959,7 +7959,7 @@ python early: need_redraw = True break - for exp_list in self.exp_map.itervalues(): + for exp_list in self.exp_map.values(): for exp_id in range(len(exp_list)-1, -1, -1): if exp_list[exp_id].tag == tag: exp_list.pop(exp_id) @@ -8997,8 +8997,9 @@ python early: # self.up_eyes_code: self.up_eyes_img, # self.down_eyes_code: self.down_eyes_img } - for first_img_code in img_map.iterkeys(): - for second_img_code in img_map.iterkeys(): + + for first_img_code in img_map.keys(): + for second_img_code in img_map.keys(): if first_img_code != second_img_code: self.transform_map[(first_img_code, second_img_code)] = _MASMoniFollowTransformDissolve( time=MASMoniFollowTransform.DIS_DUR, diff --git a/Monika After Story/game/sprite-decoder.rpy b/Monika After Story/game/sprite-decoder.rpy index ad23486562..f70fbdaa0b 100644 --- a/Monika After Story/game/sprite-decoder.rpy +++ b/Monika After Story/game/sprite-decoder.rpy @@ -57,7 +57,7 @@ init python in mas_sprite_decoder: #Since tuples aren't supported in json, we need to do some conversion here ARM_MAP["5"] = tuple(ARM_MAP["5"]) - for side_key, side_list in SIDES_MAP.iteritems(): + for side_key, side_list in SIDES_MAP.items(): SIDES_MAP[side_key] = tuple(side_list) #I don't really like this but it's a cleaner way of bringing up this exception once instead of multiple times diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index 6656f4374d..793b88cc78 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -410,10 +410,10 @@ init 25 python in mas_ui: # Methods for twopane_scrollable_menu TWOPANE_MENU_MAX_FLT_ITEMS = 50 TWOPANE_MENU_SEARCH_DBS = ( - store.mas_all_ev_db_map["EVE"].values() + list(store.mas_all_ev_db_map["EVE"].values()) # + store.mas_all_ev_db_map["BYE"].values() # + store.mas_all_ev_db_map["STY"].values() - + store.mas_all_ev_db_map["CMP"].values() + + list(store.mas_all_ev_db_map["CMP"].values()) # + store.mas_all_ev_db_map["SNG"].values() ) TWOPANE_MENU_DELEGATES_CALLBACK_MAP = { diff --git a/Monika After Story/game/updates.rpy b/Monika After Story/game/updates.rpy index ed42586023..d3659da898 100644 --- a/Monika After Story/game/updates.rpy +++ b/Monika After Story/game/updates.rpy @@ -536,7 +536,7 @@ label v0_11_9_1(version="v0_11_9_1"): # We don't use this var anymore safeDel("chess_strength") - for story_type, story_last_seen in persistent._mas_last_seen_new_story.iteritems(): + for story_type, story_last_seen in persistent._mas_last_seen_new_story.items(): if story_last_seen is not None: persistent._mas_last_seen_new_story[story_type] = datetime.datetime.combine( story_last_seen, datetime.time() diff --git a/Monika After Story/game/zz_pianokeys.rpy b/Monika After Story/game/zz_pianokeys.rpy index 9d85589248..3d9d56de47 100644 --- a/Monika After Story/game/zz_pianokeys.rpy +++ b/Monika After Story/game/zz_pianokeys.rpy @@ -1973,7 +1973,7 @@ init 800 python in mas_piano_keys: # We only include stock songs if the player's played them successfully before song_list = [ (pnml.name, pnml, False, False) - for pnml in pnml_db.itervalues() + for pnml in pnml_db.values() if (pnml.name not in STOCK_SONG_NAMES or pnml.wins > 0) ] @@ -2678,7 +2678,7 @@ init 810 python: # NOTE: highly recommend not adding too many detections self.pnml_list = [] if self.mode == self.MODE_FREE: - for _pnml in mas_piano_keys.pnml_db.itervalues(): + for _pnml in mas_piano_keys.pnml_db.values(): if _pnml.wins == 0: self.pnml_list.append(_pnml) _pnml._gen_pnm_sprites() diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 22195b5805..50524adbb2 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -709,7 +709,7 @@ init python: return active_window_handle = mas_getActiveWindowHandle() - for ev_label, ev in mas_windowreacts.windowreact_db.iteritems(): + for ev_label, ev in mas_windowreacts.windowreact_db.items(): if ( Event._filterEvent(ev, unlocked=True, aff=store.mas_curr_affection) and ev.checkConditional() @@ -730,7 +730,7 @@ init python: IN: List of ev_labels to exclude from being unlocked """ - for ev_label, ev in mas_windowreacts.windowreact_db.iteritems(): + for ev_label, ev in mas_windowreacts.windowreact_db.items(): if ev_label not in excluded: ev.unlocked=True From 686dab75baf8338b5b7fd72cf02e9821385e807f Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 30 Aug 2021 23:32:48 -0400 Subject: [PATCH 032/180] override this to not raise exceptions for dev --- Monika After Story/game/overrides.rpy | 74 ++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/overrides.rpy b/Monika After Story/game/overrides.rpy index f5b9278831..25034132af 100644 --- a/Monika After Story/game/overrides.rpy +++ b/Monika After Story/game/overrides.rpy @@ -19,5 +19,75 @@ init -10 python: ## Super early overrides ## You'll need a block like this for creator defined screen language ## Don't use this unless you know you need it -python early: - pass +python early in mas_overrides: + import ast + from renpy.python import py_eval_bytecode + from renpy.pyanalysis import ccache + + def slblock_prepare(self, analysis): + + + for i in self.children: + i.prepare(analysis) + self.constant = min(self.constant, i.constant) + + # Compile the keywords. + + keyword_values = { } + keyword_keys = [ ] + keyword_exprs = [ ] + + for k, expr in self.keyword: + + node = ccache.ast_eval(expr) + + const = analysis.is_constant(node) + + if const == renpy.sl2.slast.GLOBAL_CONST: + keyword_values[k] = py_eval_bytecode(renpy.sl2.slast.compile_expr(self.location, node)) + else: + keyword_keys.append(ast.Str(s=k)) + keyword_exprs.append(node) # Will be compiled as part of ast.Dict below. + + self.constant = min(self.constant, const) + + if keyword_values: + self.keyword_values = keyword_values + else: + self.keyword_values = None + + if keyword_keys: + node = ast.Dict(keys=keyword_keys, values=keyword_exprs) + ast.copy_location(node, keyword_exprs[0]) + self.keyword_exprs = renpy.sl2.slast.compile_expr(self.location, node) + else: + self.keyword_exprs = None + + self.has_keyword = bool(self.keyword) + self.keyword_children = [ ] + + if self.atl_transform is not None: + self.has_keyword = True + + self.atl_transform.mark_constant() + const = self.atl_transform.constant + self.constant = min(self.constant, const) + + was_last_keyword = False + for i in self.children: + if i.has_keyword: + + #Disable this exception because it's stupid + #if was_last_keyword: + # raise Exception("Properties are not allowed here.") + + self.keyword_children.append(i) + self.has_keyword = True + + if i.last_keyword: + self.last_keyword = True + was_last_keyword = True + if not renpy.config.developer: + break + + renpy.sl2.slast.SLBlock.prepare = slblock_prepare From 8d1501f4a6e69c5da989b3cd4907cf2a8316b755 Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 31 Aug 2021 01:03:19 -0400 Subject: [PATCH 033/180] rm override --- Monika After Story/game/overrides.rpy | 72 +-------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/Monika After Story/game/overrides.rpy b/Monika After Story/game/overrides.rpy index 25034132af..a10f494a47 100644 --- a/Monika After Story/game/overrides.rpy +++ b/Monika After Story/game/overrides.rpy @@ -20,74 +20,4 @@ init -10 python: ## You'll need a block like this for creator defined screen language ## Don't use this unless you know you need it python early in mas_overrides: - import ast - from renpy.python import py_eval_bytecode - from renpy.pyanalysis import ccache - - def slblock_prepare(self, analysis): - - - for i in self.children: - i.prepare(analysis) - self.constant = min(self.constant, i.constant) - - # Compile the keywords. - - keyword_values = { } - keyword_keys = [ ] - keyword_exprs = [ ] - - for k, expr in self.keyword: - - node = ccache.ast_eval(expr) - - const = analysis.is_constant(node) - - if const == renpy.sl2.slast.GLOBAL_CONST: - keyword_values[k] = py_eval_bytecode(renpy.sl2.slast.compile_expr(self.location, node)) - else: - keyword_keys.append(ast.Str(s=k)) - keyword_exprs.append(node) # Will be compiled as part of ast.Dict below. - - self.constant = min(self.constant, const) - - if keyword_values: - self.keyword_values = keyword_values - else: - self.keyword_values = None - - if keyword_keys: - node = ast.Dict(keys=keyword_keys, values=keyword_exprs) - ast.copy_location(node, keyword_exprs[0]) - self.keyword_exprs = renpy.sl2.slast.compile_expr(self.location, node) - else: - self.keyword_exprs = None - - self.has_keyword = bool(self.keyword) - self.keyword_children = [ ] - - if self.atl_transform is not None: - self.has_keyword = True - - self.atl_transform.mark_constant() - const = self.atl_transform.constant - self.constant = min(self.constant, const) - - was_last_keyword = False - for i in self.children: - if i.has_keyword: - - #Disable this exception because it's stupid - #if was_last_keyword: - # raise Exception("Properties are not allowed here.") - - self.keyword_children.append(i) - self.has_keyword = True - - if i.last_keyword: - self.last_keyword = True - was_last_keyword = True - if not renpy.config.developer: - break - - renpy.sl2.slast.SLBlock.prepare = slblock_prepare + pass From dd346a3be5b39730b1e79f2394e02b003bc71e8c Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 31 Aug 2021 01:06:59 -0400 Subject: [PATCH 034/180] kick to latest renpy --- .github/workflows/mas_check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 47b9908f0d..a0fd36a3db 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,10 +39,10 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget https://www.renpy.org/dl/7.4.4/renpy-7.4.4-sdk.tar.bz2 - tar xf renpy-7.4.4-sdk.tar.bz2 - rm renpy-7.4.4-sdk.tar.bz2 - mv renpy-7.4.4-sdk renpy + wget https://www.renpy.org/dl/7.4.8/renpy-7.4.8-sdk.tar.bz2 + tar xf renpy-7.4.8-sdk.tar.bz2 + rm renpy-7.4.8-sdk.tar.bz2 + mv renpy-7.4.8-sdk renpy # download base mas - name: Download base MAS + copy files over From 3915e1799058d4153e431e5c03feda697ec300f7 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 2 Sep 2021 00:56:12 -0400 Subject: [PATCH 035/180] change amt of predict statements --- Monika After Story/game/options.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index dc7a60c861..64aa880340 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -153,7 +153,7 @@ define config.autosave_slots = 0 define config.layers = ["master", "transient", "minigames", "screens", "overlay", "front"] define config.image_cache_size = 64 define config.debug_image_cache = config.developer -define config.predict_statements = 5 +define config.predict_statements = 32 define config.rollback_enabled = config.developer define config.menu_clear_layers = ["front"] define config.gl_test_image = "white" From 5d8a4d77baeaf9ca674bde1687e411db037728f8 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 2 Sep 2021 18:52:36 -0400 Subject: [PATCH 036/180] always fetch latest renpy --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index a0fd36a3db..307522ca60 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,7 +39,7 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget https://www.renpy.org/dl/7.4.8/renpy-7.4.8-sdk.tar.bz2 + wget $(wget -qO- https://www.renpy.org/latest.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2') tar xf renpy-7.4.8-sdk.tar.bz2 rm renpy-7.4.8-sdk.tar.bz2 mv renpy-7.4.8-sdk renpy From f5412b053942b6d8685c8e2dd05cdfe944393ad3 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 4 Sep 2021 21:04:30 -0400 Subject: [PATCH 037/180] fetch latest nightly --- .github/workflows/mas_check.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 307522ca60..6b3b045ca9 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,10 +39,11 @@ jobs: # dl renpy src - name: Download rpy source run: | - wget $(wget -qO- https://www.renpy.org/latest.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2') - tar xf renpy-7.4.8-sdk.tar.bz2 - rm renpy-7.4.8-sdk.tar.bz2 - mv renpy-7.4.8-sdk renpy + renpysdk=$(wget -qO- https://nightly.renpy.org/current/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') + wget https://nightly.renpy.org/current/$renpysdk + tar xf $renpysdk + rm $renpysdk + mv ${renpysdk/.tar.bz2/} renpy # download base mas - name: Download base MAS + copy files over From 1f9d20cbf35dccc9c55d7a90a09c1f5ed2b42924 Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 7 Sep 2021 08:44:53 -0400 Subject: [PATCH 038/180] Depreciate mas_getMousePost as inaccurate. TODO: Chess is EXTREMELY broken right now, need to fix a bunch of the coords --- Monika After Story/game/chess.rpy | 6 +++--- Monika After Story/game/definitions.rpy | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 1055a5a834..c08eb37522 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -2414,7 +2414,7 @@ init python: highlight_magenta = renpy.render(MASChessDisplayableBase.PIECE_HIGHLIGHT_MAGENTA_IMAGE, 1280, 720, st, at) #Get our mouse pos - mx, my = mas_getMousePos() + mx, my = renpy.get_mouse_pos() #Since different buttons show during the game vs post game, we'll sort out what's shown here visible_buttons = list() @@ -2562,7 +2562,7 @@ init python: #Draw the selected piece. piece = self.get_piece_at(self.selected_piece[0], self.selected_piece[1]) - px, py = mas_getMousePos() + px, py = renpy.get_mouse_pos() px -= MASChessDisplayableBase.PIECE_WIDTH / 2 py -= MASChessDisplayableBase.PIECE_HEIGHT / 2 piece.render(width, height, st, at, px, py, renderer) @@ -2640,7 +2640,7 @@ init python: OUT: Tuple of coordinates (x, y) marking where the piece is """ - mx, my = mas_getMousePos() + mx, my = renpy.get_mouse_pos() mx -= MASChessDisplayableBase.BASE_PIECE_X my -= MASChessDisplayableBase.BASE_PIECE_Y px = mx / MASChessDisplayableBase.PIECE_WIDTH diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 4fe3c42275..81f6ed1685 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -6630,8 +6630,13 @@ init 21 python: return rv + @store.mas_utils.deprecated(use_instead="renpy.get_mouse_pos", should_raise=True) def mas_getMousePos(): """ + DEPRECIATED + + Use renpy.get_mouse_pos instead + Gets the mouse position in terms of physical screen size OUT: From 6b40510b0d0c35fd057add5f7e17b608b4d7a26f Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 09:49:45 -0500 Subject: [PATCH 039/180] CI is now checking r8, no more rpy python 3 --- .github/workflows/mas_check.yml | 2 +- .../game/dev/dev_backgrounds.rpy | 1 - .../game/dev/dev_exp_previewer.rpy | 1 - Monika After Story/game/options.rpy | 1 - Monika After Story/game/script-ch30.rpy | 1 - Monika After Story/game/script-moods.rpy | 1 - Monika After Story/game/script-python.rpy | 1 - Monika After Story/game/script-songs.rpy | 1 - Monika After Story/game/script-stories.rpy | 1 - .../game/script-story-events.rpy | 1 - Monika After Story/game/script-topics.rpy | 1 - Monika After Story/game/script.rpy | 1 - Monika After Story/game/shake.rpy | 1 - Monika After Story/game/special-effects.rpy | 1 - Monika After Story/game/splash.rpy | 1 - .../game/sprite-chart-matrix.rpy | 3 +-- Monika After Story/game/sprite-chart.rpy | 1 - Monika After Story/game/sprite-decoder.rpy | 1 - Monika After Story/game/sprite-generator.rpy | 1 - Monika After Story/game/styles.rpy | 1 - Monika After Story/game/updater.rpy | 1 - Monika After Story/game/updates.rpy | 1 - Monika After Story/game/updates_topics.rpy | 1 - Monika After Story/game/zz_backgrounds.rpy | 1 - Monika After Story/game/zz_backup.rpy | 21 ++++++++----------- Monika After Story/game/zz_calendar.rpy | 1 - Monika After Story/game/zz_consumables.rpy | 1 - Monika After Story/game/zz_dm.rpy | 1 - Monika After Story/game/zz_dockingstation.rpy | 1 - Monika After Story/game/zz_dump.rpy | 1 - Monika After Story/game/zz_extrasmenu.rpy | 1 - Monika After Story/game/zz_games.rpy | 1 - Monika After Story/game/zz_graphicsmenu.rpy | 1 - Monika After Story/game/zz_hangman.rpy | 1 - Monika After Story/game/zz_history.rpy | 1 - Monika After Story/game/zz_hotkey_buttons.rpy | 1 - Monika After Story/game/zz_hotkeys.rpy | 1 - Monika After Story/game/zz_interactions.rpy | 1 - Monika After Story/game/zz_monikamovie.rpy | 1 - Monika After Story/game/zz_music_selector.rpy | 1 - Monika After Story/game/zz_overlays.rpy | 1 - Monika After Story/game/zz_pianokeys.rpy | 1 - Monika After Story/game/zz_poemgame.rpy | 1 - Monika After Story/game/zz_poems.rpy | 1 - Monika After Story/game/zz_reactions.rpy | 1 - Monika After Story/game/zz_seasons.rpy | 1 - Monika After Story/game/zz_selector.rpy | 1 - Monika After Story/game/zz_shields.rpy | 1 - Monika After Story/game/zz_spritedeco.rpy | 1 - Monika After Story/game/zz_spritejsons.rpy | 1 - Monika After Story/game/zz_spriteobjects.rpy | 1 - Monika After Story/game/zz_submods.rpy | 1 - Monika After Story/game/zz_threading.rpy | 1 - Monika After Story/game/zz_transforms.rpy | 1 - Monika After Story/game/zz_weather.rpy | 1 - 55 files changed, 11 insertions(+), 67 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index f1e1203807..2a443fb332 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -46,7 +46,7 @@ jobs: - name: Download rpy source if: steps.cache-rpy.outputs.cache-hit != 'true' run: | - renpysdk=$(wget -qO- https://nightly.renpy.org/current/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') + renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') wget https://nightly.renpy.org/current/$renpysdk tar xf $renpysdk rm $renpysdk diff --git a/Monika After Story/game/dev/dev_backgrounds.rpy b/Monika After Story/game/dev/dev_backgrounds.rpy index c96e2ebf1d..c5cb6f7b85 100644 --- a/Monika After Story/game/dev/dev_backgrounds.rpy +++ b/Monika After Story/game/dev/dev_backgrounds.rpy @@ -1,4 +1,3 @@ -rpy python 3 # test module for backgrounds init 100 python: diff --git a/Monika After Story/game/dev/dev_exp_previewer.rpy b/Monika After Story/game/dev/dev_exp_previewer.rpy index 320ccf76cb..6aec0eef58 100644 --- a/Monika After Story/game/dev/dev_exp_previewer.rpy +++ b/Monika After Story/game/dev/dev_exp_previewer.rpy @@ -1,4 +1,3 @@ -rpy python 3 # special module that contains a screen dedicated to expression prevewing. # we really needed this lol. diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 57b257773f..9d31f40c66 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## This file contains options that can be changed to customize your game. ## ## Lines beginning with two '#' marks are comments, and you shouldn't uncomment diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 19c0b9af20..593e5b8bc8 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -1,4 +1,3 @@ -rpy python 3 default persistent.monika_reload = 0 # Has the player tried to skip? # (this is None because the player might tried to skip, but hasn't merged the saves yet) diff --git a/Monika After Story/game/script-moods.rpy b/Monika After Story/game/script-moods.rpy index de94ee731d..cfb404308a 100644 --- a/Monika After Story/game/script-moods.rpy +++ b/Monika After Story/game/script-moods.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module that handles the mood system # diff --git a/Monika After Story/game/script-python.rpy b/Monika After Story/game/script-python.rpy index c6cf616b54..d3e13a8c4a 100644 --- a/Monika After Story/game/script-python.rpy +++ b/Monika After Story/game/script-python.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Monika's Python Tip of the Day (PTOD) # # I probably will be adding many of these, so For the sake of organization diff --git a/Monika After Story/game/script-songs.rpy b/Monika After Story/game/script-songs.rpy index 3fe9188a4f..e755a9e8ec 100644 --- a/Monika After Story/game/script-songs.rpy +++ b/Monika After Story/game/script-songs.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Event database for songs default persistent._mas_songs_database = dict() diff --git a/Monika After Story/game/script-stories.rpy b/Monika After Story/game/script-stories.rpy index dbeedfce87..188b019dd1 100644 --- a/Monika After Story/game/script-stories.rpy +++ b/Monika After Story/game/script-stories.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module for Monika story telling # # Stories will get unlocked one at by session diff --git a/Monika After Story/game/script-story-events.rpy b/Monika After Story/game/script-story-events.rpy index ad0325bc3f..8ae90b25d8 100644 --- a/Monika After Story/game/script-story-events.rpy +++ b/Monika After Story/game/script-story-events.rpy @@ -1,4 +1,3 @@ -rpy python 3 #This file will include short story events that don't require their own file. #An event is crated by only adding a label and adding a requirement (see comment below). diff --git a/Monika After Story/game/script-topics.rpy b/Monika After Story/game/script-topics.rpy index 2f1f875bf5..dc5e639288 100644 --- a/Monika After Story/game/script-topics.rpy +++ b/Monika After Story/game/script-topics.rpy @@ -1,4 +1,3 @@ -rpy python 3 #This file contains all of monika's topics she can talk about #Each entry should start with a database entry, including the appropriate flags #to either be a random topic, a prompt "pool" topics, or a special conditional diff --git a/Monika After Story/game/script.rpy b/Monika After Story/game/script.rpy index f4b9fc7220..28b955a0a2 100644 --- a/Monika After Story/game/script.rpy +++ b/Monika After Story/game/script.rpy @@ -1,4 +1,3 @@ -rpy python 3 # This is used for top-level game strucutre. # Should not include any actual events or scripting; only logic and calling other labels. # diff --git a/Monika After Story/game/shake.rpy b/Monika After Story/game/shake.rpy index fc43fea1b8..6ce4035da1 100644 --- a/Monika After Story/game/shake.rpy +++ b/Monika After Story/game/shake.rpy @@ -1,4 +1,3 @@ -rpy python 3 init: python: diff --git a/Monika After Story/game/special-effects.rpy b/Monika After Story/game/special-effects.rpy index 0ceb86072d..bafa648604 100644 --- a/Monika After Story/game/special-effects.rpy +++ b/Monika After Story/game/special-effects.rpy @@ -1,4 +1,3 @@ -rpy python 3 # This file is meant to store any special effects. # These can be some images or transforms. init -500 python in mas_parallax: diff --git a/Monika After Story/game/splash.rpy b/Monika After Story/game/splash.rpy index b8c4722dcc..451957f5f5 100644 --- a/Monika After Story/game/splash.rpy +++ b/Monika After Story/game/splash.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## This splash screen is the first thing that Renpy will show the player ## ## Before load, check to be sure that the archive files were found. diff --git a/Monika After Story/game/sprite-chart-matrix.rpy b/Monika After Story/game/sprite-chart-matrix.rpy index 18653a4e01..bb6ed056f9 100644 --- a/Monika After Story/game/sprite-chart-matrix.rpy +++ b/Monika After Story/game/sprite-chart-matrix.rpy @@ -1,4 +1,3 @@ -rpy python 3 # sprite generation using matrix for night sprites # TODO: look at adding a highlight option to ACS/Clothes/Hair @@ -1451,7 +1450,7 @@ init -4 python in mas_sprites: """ img_key, cid, img_base, hl_base = render_key if cid == CID_DYNAMIC: - # add the img and hl directly + # add the img and hl directly if img_base is not None: render_list.append(img_base) if hl_base is not None: diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index ee3ada937e..a4e9c365c1 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Monika's sprites! # To add new images, use the sprite adder tool in MonikaModDev/tools/toolsmenu # diff --git a/Monika After Story/game/sprite-decoder.rpy b/Monika After Story/game/sprite-decoder.rpy index 126d117d09..04a6240e7b 100644 --- a/Monika After Story/game/sprite-decoder.rpy +++ b/Monika After Story/game/sprite-decoder.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Runtime code equivalent of our spritemaker tool init python in mas_sprite_decoder: import json diff --git a/Monika After Story/game/sprite-generator.rpy b/Monika After Story/game/sprite-generator.rpy index a48a15a3f7..a08da1643a 100644 --- a/Monika After Story/game/sprite-generator.rpy +++ b/Monika After Story/game/sprite-generator.rpy @@ -1,4 +1,3 @@ -rpy python 3 init python in mas_sprites: #START: main funcs diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index d7c7507779..93fc933cab 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -1,4 +1,3 @@ -rpy python 3 # START: vars # Whether dark mode is enabled or not default persistent._mas_dark_mode_enabled = False diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index 30cd865724..e86198d13c 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -1,4 +1,3 @@ -rpy python 3 # enabling unstable mode default persistent._mas_unstable_mode = False default persistent._mas_can_update = True diff --git a/Monika After Story/game/updates.rpy b/Monika After Story/game/updates.rpy index e5baada217..f4c2270f6f 100644 --- a/Monika After Story/game/updates.rpy +++ b/Monika After Story/game/updates.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that handles updates between versions # Assumes: # updates.topics diff --git a/Monika After Story/game/updates_topics.rpy b/Monika After Story/game/updates_topics.rpy index 28d3122c02..117f65fa57 100644 --- a/Monika After Story/game/updates_topics.rpy +++ b/Monika After Story/game/updates_topics.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that defines changed topics between versions # this should run before updates.rpy diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index 769fe5c2e0..200ac1685c 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Here's where we store our background data default persistent._mas_background_MBGdata = {} diff --git a/Monika After Story/game/zz_backup.rpy b/Monika After Story/game/zz_backup.rpy index f00cff17e4..a3e2872d5a 100644 --- a/Monika After Story/game/zz_backup.rpy +++ b/Monika After Story/game/zz_backup.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module that does some file backup work # NOTE: these shoudl never be true for a standard persistent. @@ -194,7 +193,7 @@ python early in mas_per_check: def should_show_chibika_persistent(): """ - Should we show the chibika persistent dialogue? + Should we show the chibika persistent dialogue? RETURNS: True if we should show the chibika persistent dialogue """ @@ -240,7 +239,7 @@ python early in mas_per_check: # first, check if we have a special persistent if os.access(_sp_per, os.F_OK): - # we have one, so check if its valid + # we have one, so check if its valid try: # TEST_CASE_A per_read, version = tryper(_sp_per) @@ -368,7 +367,7 @@ python early in mas_per_check: try: # TEST_CASE_F shutil.copy(_cur_per, _sp_per) - os.remove(_cur_per) + os.remove(_cur_per) # and then close out of here - the game should generate a fresh # persistent. @@ -845,7 +844,7 @@ label mas_backups_incompat_start: $ mas_darkMode(True) # required for the updater if ( - persistent._mas_incompat_per_rpy_files_found + persistent._mas_incompat_per_rpy_files_found and mas_hasRPYFiles() ): # user said they would delete the RPY files, but we still have them @@ -985,7 +984,7 @@ label mas_backups_incompat_rpy_yes_del: show chibika 3 at sticker_hop "Done!" "Let's try updating now!" - jump mas_backups_incompat_updater_start + jump mas_backups_incompat_updater_start label mas_backups_incompat_rpy_no_del: @@ -1023,8 +1022,8 @@ label mas_backups_incompat_updater_failed: # fall through label mas_backups_incompat_updater_start: - - # setup for unstable + + # setup for unstable $ persistent._mas_unstable_mode = True $ mas_updater.force = True @@ -1045,10 +1044,10 @@ label mas_backups_incompat_updater_start: # NOTE: why don't we lock or remove the cancel button? The user # might have their own reasons for canceling the update check: # - maybe they are on low bandwidth/metered connections? - # - maybe they actually want to stay on stable and have a backup + # - maybe they actually want to stay on stable and have a backup # persistent to us? # - maybe its maybelline? - # either way, since the user has an unstable per, no need for + # either way, since the user has an unstable per, no need for # extravagant handholding. #"hol up" # use this to debug cancel returns @@ -1076,5 +1075,3 @@ label mas_backups_incompat_updater_start: "Good luck!" jump _quit - - diff --git a/Monika After Story/game/zz_calendar.rpy b/Monika After Story/game/zz_calendar.rpy index 7f03bdd5dc..5285062b00 100644 --- a/Monika After Story/game/zz_calendar.rpy +++ b/Monika After Story/game/zz_calendar.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Calendar module # A custom made Calendar like UI to help managing date based events # Contains also a store named mas_calendar which includes helper functions diff --git a/Monika After Story/game/zz_consumables.rpy b/Monika After Story/game/zz_consumables.rpy index 542af34580..0337d9c144 100644 --- a/Monika After Story/game/zz_consumables.rpy +++ b/Monika After Story/game/zz_consumables.rpy @@ -1,4 +1,3 @@ -rpy python 3 default persistent._mas_current_consumable = { 0: { "prep_time": None, diff --git a/Monika After Story/game/zz_dm.rpy b/Monika After Story/game/zz_dm.rpy index 6f686eac56..6cd403c820 100644 --- a/Monika After Story/game/zz_dm.rpy +++ b/Monika After Story/game/zz_dm.rpy @@ -1,4 +1,3 @@ -rpy python 3 # data migration module init -999 python in _mas_dm_dm: diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 6548ab1932..2123cae88d 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that provides an interface for loading / saving files that we interact with # # NOTE: this is meant purely for reading / writing files into base64 with diff --git a/Monika After Story/game/zz_dump.rpy b/Monika After Story/game/zz_dump.rpy index c357114a3e..a9a0480df5 100644 --- a/Monika After Story/game/zz_dump.rpy +++ b/Monika After Story/game/zz_dump.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## dumps file for unstablers init 999 python: diff --git a/Monika After Story/game/zz_extrasmenu.rpy b/Monika After Story/game/zz_extrasmenu.rpy index 256abf7c12..d574c9a1e2 100644 --- a/Monika After Story/game/zz_extrasmenu.rpy +++ b/Monika After Story/game/zz_extrasmenu.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module containing what we call interactive modes (extras) # basically things like headpats and other mouse-based interactions should be # defined here diff --git a/Monika After Story/game/zz_games.rpy b/Monika After Story/game/zz_games.rpy index 18d6e13294..ba6e91a9b6 100644 --- a/Monika After Story/game/zz_games.rpy +++ b/Monika After Story/game/zz_games.rpy @@ -1,4 +1,3 @@ -rpy python 3 default persistent._mas_game_database = dict() init -10 python in mas_games: diff --git a/Monika After Story/game/zz_graphicsmenu.rpy b/Monika After Story/game/zz_graphicsmenu.rpy index 1996c5f165..7e5c31f55a 100644 --- a/Monika After Story/game/zz_graphicsmenu.rpy +++ b/Monika After Story/game/zz_graphicsmenu.rpy @@ -1,4 +1,3 @@ -rpy python 3 # graphics selection menu # we do this instead of the actual one because the real one breaks everything diff --git a/Monika After Story/game/zz_hangman.rpy b/Monika After Story/game/zz_hangman.rpy index 4c20b56b94..ce919dc6fe 100644 --- a/Monika After Story/game/zz_hangman.rpy +++ b/Monika After Story/game/zz_hangman.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that does hangman man # # DEPENDS ON: diff --git a/Monika After Story/game/zz_history.rpy b/Monika After Story/game/zz_history.rpy index c4338ff18d..ed089dddf8 100644 --- a/Monika After Story/game/zz_history.rpy +++ b/Monika After Story/game/zz_history.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Historical data module # # How this works: diff --git a/Monika After Story/game/zz_hotkey_buttons.rpy b/Monika After Story/game/zz_hotkey_buttons.rpy index 6aa2d25cf6..30ddb523e9 100644 --- a/Monika After Story/game/zz_hotkey_buttons.rpy +++ b/Monika After Story/game/zz_hotkey_buttons.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that handles hotkey button screen # diff --git a/Monika After Story/game/zz_hotkeys.rpy b/Monika After Story/game/zz_hotkeys.rpy index 45700effb2..6f0d2e6745 100644 --- a/Monika After Story/game/zz_hotkeys.rpy +++ b/Monika After Story/game/zz_hotkeys.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that is just for hotkeys and other keymaps # diff --git a/Monika After Story/game/zz_interactions.rpy b/Monika After Story/game/zz_interactions.rpy index 771cb39a0e..387cd54b62 100644 --- a/Monika After Story/game/zz_interactions.rpy +++ b/Monika After Story/game/zz_interactions.rpy @@ -1,4 +1,3 @@ -rpy python 3 # all complicated interactions go here # mainly: # boop diff --git a/Monika After Story/game/zz_monikamovie.rpy b/Monika After Story/game/zz_monikamovie.rpy index e68f2d8a41..c55b182308 100644 --- a/Monika After Story/game/zz_monikamovie.rpy +++ b/Monika After Story/game/zz_monikamovie.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Watch a movie module # diff --git a/Monika After Story/game/zz_music_selector.rpy b/Monika After Story/game/zz_music_selector.rpy index 566d0342d4..09be225849 100644 --- a/Monika After Story/game/zz_music_selector.rpy +++ b/Monika After Story/game/zz_music_selector.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that handles the music selection screen # we start with zz to ensure this loads LAST # diff --git a/Monika After Story/game/zz_overlays.rpy b/Monika After Story/game/zz_overlays.rpy index 1202c2b050..18fa07054f 100644 --- a/Monika After Story/game/zz_overlays.rpy +++ b/Monika After Story/game/zz_overlays.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module for handling of overlay screens nicely # NOTE: do not write screen overlays that need to be past 500 init level. # this should be ran last to setup proper linkages diff --git a/Monika After Story/game/zz_pianokeys.rpy b/Monika After Story/game/zz_pianokeys.rpy index 87e09c57dd..9cff0e74a9 100644 --- a/Monika After Story/game/zz_pianokeys.rpy +++ b/Monika After Story/game/zz_pianokeys.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that lets you play the piano # # Adding custom Piano Songs: diff --git a/Monika After Story/game/zz_poemgame.rpy b/Monika After Story/game/zz_poemgame.rpy index 0692c66687..8814164e25 100644 --- a/Monika After Story/game/zz_poemgame.rpy +++ b/Monika After Story/game/zz_poemgame.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that contains a modified version of the poem minigame so we can use # it seamlessly in topics # diff --git a/Monika After Story/game/zz_poems.rpy b/Monika After Story/game/zz_poems.rpy index 6a158b4eda..59b68b0e5e 100644 --- a/Monika After Story/game/zz_poems.rpy +++ b/Monika After Story/game/zz_poems.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Dict holding seen poems and amount of times seen #poem_id:shown_count default persistent._mas_poems_seen = dict() diff --git a/Monika After Story/game/zz_reactions.rpy b/Monika After Story/game/zz_reactions.rpy index 5ec496a960..32e1d1d231 100644 --- a/Monika After Story/game/zz_reactions.rpy +++ b/Monika After Story/game/zz_reactions.rpy @@ -1,4 +1,3 @@ -rpy python 3 # FileReactions framework. # not too different from events diff --git a/Monika After Story/game/zz_seasons.rpy b/Monika After Story/game/zz_seasons.rpy index 1cab00bdd1..d23edb624b 100644 --- a/Monika After Story/game/zz_seasons.rpy +++ b/Monika After Story/game/zz_seasons.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## seasonal module. # contains season functions and seasonal programming points. diff --git a/Monika After Story/game/zz_selector.rpy b/Monika After Story/game/zz_selector.rpy index 9c5837fbed..4ae1968c3b 100644 --- a/Monika After Story/game/zz_selector.rpy +++ b/Monika After Story/game/zz_selector.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## module that contains a workable selection screen? # # NOTE: i have no idea how generic this can get. diff --git a/Monika After Story/game/zz_shields.rpy b/Monika After Story/game/zz_shields.rpy index 70e3841e02..4f65e5db60 100644 --- a/Monika After Story/game/zz_shields.rpy +++ b/Monika After Story/game/zz_shields.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that contains both work-flow specific shield functions and # generalized shield functions. # diff --git a/Monika After Story/game/zz_spritedeco.rpy b/Monika After Story/game/zz_spritedeco.rpy index 27bd91ce2e..106dfdf5d8 100644 --- a/Monika After Story/game/zz_spritedeco.rpy +++ b/Monika After Story/game/zz_spritedeco.rpy @@ -1,4 +1,3 @@ -rpy python 3 # large rewrite incoming init -700 python in mas_deco: diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index 1f5303294b..3e707fab88 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module for turning json formats into sprite objects # NOTE: This DEPENDS on sprite-chart.rpy and sprite-chart-matrix.rpy # diff --git a/Monika After Story/game/zz_spriteobjects.rpy b/Monika After Story/game/zz_spriteobjects.rpy index c730b75c78..635882a801 100644 --- a/Monika After Story/game/zz_spriteobjects.rpy +++ b/Monika After Story/game/zz_spriteobjects.rpy @@ -1,4 +1,3 @@ -rpy python 3 # All Sprite objects belong here # # For documentation on classes, see sprite-chart diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index 48a9a17673..ff69c97eaa 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -1,4 +1,3 @@ -rpy python 3 init -999: default persistent._mas_submod_version_data = dict() diff --git a/Monika After Story/game/zz_threading.rpy b/Monika After Story/game/zz_threading.rpy index a9da6cb935..4831436730 100644 --- a/Monika After Story/game/zz_threading.rpy +++ b/Monika After Story/game/zz_threading.rpy @@ -1,4 +1,3 @@ -rpy python 3 init -750 python in mas_threading: # threading related vars import threading diff --git a/Monika After Story/game/zz_transforms.rpy b/Monika After Story/game/zz_transforms.rpy index 27456047c6..864d03f1a3 100644 --- a/Monika After Story/game/zz_transforms.rpy +++ b/Monika After Story/game/zz_transforms.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module containing custom transform functions. # Last just because # NOTE: Depends on script-poemgame diff --git a/Monika After Story/game/zz_weather.rpy b/Monika After Story/game/zz_weather.rpy index 2e0698284a..99c68c5986 100644 --- a/Monika After Story/game/zz_weather.rpy +++ b/Monika After Story/game/zz_weather.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Stores the last weather the player had chosen #Default: "auto" default persistent._mas_current_weather = "auto" From 425350df71ae70016d80721be8eba88b08fa94b0 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 10:30:42 -0500 Subject: [PATCH 040/180] `there were more` --- Monika After Story/game/chess.rpy | 2 -- Monika After Story/game/definitions.rpy | 1 - .../game/dev/dev_active_window_check.rpy | 1 - .../game/dev/dev_affection-test.rpy | 1 - Monika After Story/game/dev/dev_bday.rpy | 1 - Monika After Story/game/dev/dev_calendar.rpy | 1 - .../game/dev/dev_check_scrollable_menu_test.rpy | 1 - Monika After Story/game/dev/dev_d25_resets.rpy | 1 - Monika After Story/game/dev/dev_db.rpy | 15 +++++++-------- Monika After Story/game/dev/dev_deco.rpy | 3 +-- Monika After Story/game/dev/dev_ds_testing.rpy | 1 - Monika After Story/game/dev/dev_farewells.rpy | 1 - Monika After Story/game/dev/dev_greetings.rpy | 1 - Monika After Story/game/dev/dev_idle_test.rpy | 1 - .../game/dev/dev_in_active_window_check.rpy | 1 - Monika After Story/game/dev/dev_kissing_test.rpy | 1 - Monika After Story/game/dev/dev_moods.rpy | 1 - Monika After Story/game/dev/dev_mouse_tracker.rpy | 1 - Monika After Story/game/dev/dev_overlays.rpy | 1 - Monika After Story/game/dev/dev_pg_topics.rpy | 1 - Monika After Story/game/dev/dev_pong.rpy | 1 - Monika After Story/game/dev/dev_python.rpy | 1 - Monika After Story/game/dev/dev_selector.rpy | 1 - Monika After Story/game/dev/dev_sprites.rpy | 7 +++---- Monika After Story/game/dev/dev_tools.rpy | 1 - Monika After Story/game/dev/dev_unittest.rpy | 1 - Monika After Story/game/dev/dev_weather.rpy | 1 - Monika After Story/game/dev/dev_xp.rpy | 2 -- .../game/dev/dev_zoom_transition.rpy | 1 - Monika After Story/game/dev/zz_dump.rpy | 1 - Monika After Story/game/event-handler.rpy | 1 - Monika After Story/game/event-rules.rpy | 1 - Monika After Story/game/import_ddlc.rpy | 1 - Monika After Story/game/main_menu.rpy | 1 - Monika After Story/game/pong.rpy | 1 - Monika After Story/game/progression.rpy | 1 - Monika After Story/game/screens.rpy | 1 - Monika After Story/game/script-affection.rpy | 1 - Monika After Story/game/script-anniversary.rpy | 1 - Monika After Story/game/script-apologies.rpy | 1 - Monika After Story/game/script-brbs.rpy | 1 - Monika After Story/game/script-compliments.rpy | 1 - Monika After Story/game/script-easter-eggs.rpy | 1 - Monika After Story/game/script-farewells.rpy | 1 - Monika After Story/game/script-fun-facts.rpy | 1 - Monika After Story/game/script-grammar.rpy | 1 - Monika After Story/game/script-greetings.rpy | 1 - Monika After Story/game/script-holidays.rpy | 1 - Monika After Story/game/script-introduction.rpy | 1 - Monika After Story/game/script-islands-event.rpy | 1 - 50 files changed, 11 insertions(+), 63 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 9dee8e20bd..34391df34b 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -1,5 +1,3 @@ -rpy python 3 - #We now will keep track of player wins/losses/draws/whatever default persistent._mas_chess_stats = { "wins": 0, diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 742cda7085..0d0c30a4eb 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -1,4 +1,3 @@ -rpy python 3 define persistent.demo = False define config.developer = False diff --git a/Monika After Story/game/dev/dev_active_window_check.rpy b/Monika After Story/game/dev/dev_active_window_check.rpy index eebd14d2d4..481bf86316 100644 --- a/Monika After Story/game/dev/dev_active_window_check.rpy +++ b/Monika After Story/game/dev/dev_active_window_check.rpy @@ -1,4 +1,3 @@ -rpy python 3 init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_affection-test.rpy b/Monika After Story/game/dev/dev_affection-test.rpy index 10bec01c44..96fcda064d 100644 --- a/Monika After Story/game/dev/dev_affection-test.rpy +++ b/Monika After Story/game/dev/dev_affection-test.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Affection related checks default persistent._mas_disable_sorry = None diff --git a/Monika After Story/game/dev/dev_bday.rpy b/Monika After Story/game/dev/dev_bday.rpy index 0dd61e15a2..00b3f9734e 100644 --- a/Monika After Story/game/dev/dev_bday.rpy +++ b/Monika After Story/game/dev/dev_bday.rpy @@ -1,4 +1,3 @@ -rpy python 3 # test bday art init 5 python: diff --git a/Monika After Story/game/dev/dev_calendar.rpy b/Monika After Story/game/dev/dev_calendar.rpy index 47808591a1..dd75257366 100644 --- a/Monika After Story/game/dev/dev_calendar.rpy +++ b/Monika After Story/game/dev/dev_calendar.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## calendar testing init 5 python: diff --git a/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy b/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy index fab1f3aed9..51bbd4ec11 100644 --- a/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy +++ b/Monika After Story/game/dev/dev_check_scrollable_menu_test.rpy @@ -1,4 +1,3 @@ -rpy python 3 init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_d25_resets.rpy b/Monika After Story/game/dev/dev_d25_resets.rpy index b10935f6ac..03588376a2 100644 --- a/Monika After Story/game/dev/dev_d25_resets.rpy +++ b/Monika After Story/game/dev/dev_d25_resets.rpy @@ -1,4 +1,3 @@ -rpy python 3 # just resetting the d25 events init 999 python: diff --git a/Monika After Story/game/dev/dev_db.rpy b/Monika After Story/game/dev/dev_db.rpy index 5d6d3a6185..1671ef8523 100644 --- a/Monika After Story/game/dev/dev_db.rpy +++ b/Monika After Story/game/dev/dev_db.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## special functions to check integrity of systems init python: @@ -243,8 +242,8 @@ init python in dev_mas_shared: def __init__(self, in_char): """ IN: - in_char - pass True if the persisten file is int - eh user's charactesr dir. Otherwise we use the + in_char - pass True if the persisten file is int + eh user's charactesr dir. Otherwise we use the loaded persistent """ self.in_char = in_char @@ -274,7 +273,7 @@ init python in dev_mas_shared: for prop_name in store.Event.T_EVENT_NAMES: prop_value = getattr(ev_data, prop_name) - # listables need to be split into parts + # listables need to be split into parts # except affection if ver._verify_tuli(prop_value, allow_none=False) and prop_name != "aff_range": for item in prop_value: @@ -330,7 +329,7 @@ init python in dev_mas_shared: only_incl=None ): """ - Does get_all_for_prop but saves the results directly to file + Does get_all_for_prop but saves the results directly to file See get_all_for_prop for param doc """ @@ -350,7 +349,7 @@ init python in dev_mas_shared: prop_value = getattr(curr_data, prop) - # this is just so we dont have to print out of loop + # this is just so we dont have to print out of loop if prop_value is None: last_value = "" else: @@ -378,7 +377,7 @@ init python in dev_mas_shared: False to sort descending None to not sort (Default: None) - only_incl - pass this in as a dictionary of eventlabels to + only_incl - pass this in as a dictionary of eventlabels to only include these in the prop RETURNS: tuple: @@ -416,7 +415,7 @@ init python in dev_mas_shared: IN: prop - the property to get value - the value to get - only_incl - if passed in, only include entries with + only_incl - if passed in, only include entries with eventlabel in this dict RETURNS: dictionary containing all items that match the value for diff --git a/Monika After Story/game/dev/dev_deco.rpy b/Monika After Story/game/dev/dev_deco.rpy index 68d98bf868..39c5e76e6f 100644 --- a/Monika After Story/game/dev/dev_deco.rpy +++ b/Monika After Story/game/dev/dev_deco.rpy @@ -1,4 +1,3 @@ -rpy python 3 # deco testing @@ -203,7 +202,7 @@ init 5 python: ) label dev_deco_tag_test_api_same: - + m 1eub "TIME TO TEST `register_img_same`" $ mas_showDecoTag("dev_monika_deco_thr", show_now=True) diff --git a/Monika After Story/game/dev/dev_ds_testing.rpy b/Monika After Story/game/dev/dev_ds_testing.rpy index 1c7a73ffb8..539661d9df 100644 --- a/Monika After Story/game/dev/dev_ds_testing.rpy +++ b/Monika After Story/game/dev/dev_ds_testing.rpy @@ -1,4 +1,3 @@ -rpy python 3 init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_farewells.rpy b/Monika After Story/game/dev/dev_farewells.rpy index 2d3e116905..5fb9175fed 100644 --- a/Monika After Story/game/dev/dev_farewells.rpy +++ b/Monika After Story/game/dev/dev_farewells.rpy @@ -1,4 +1,3 @@ -rpy python 3 # dev related farewells init python: diff --git a/Monika After Story/game/dev/dev_greetings.rpy b/Monika After Story/game/dev/dev_greetings.rpy index e13308f7f3..370cb33356 100644 --- a/Monika After Story/game/dev/dev_greetings.rpy +++ b/Monika After Story/game/dev/dev_greetings.rpy @@ -1,4 +1,3 @@ -rpy python 3 # dev related greetings # TODO Delete this *Insert Monika with a handgun* diff --git a/Monika After Story/game/dev/dev_idle_test.rpy b/Monika After Story/game/dev/dev_idle_test.rpy index f64b8d3824..7df2b15005 100644 --- a/Monika After Story/game/dev/dev_idle_test.rpy +++ b/Monika After Story/game/dev/dev_idle_test.rpy @@ -1,4 +1,3 @@ -rpy python 3 # testing module ofr idle init 5 python: diff --git a/Monika After Story/game/dev/dev_in_active_window_check.rpy b/Monika After Story/game/dev/dev_in_active_window_check.rpy index 058e448f9b..fbd8ed42e4 100644 --- a/Monika After Story/game/dev/dev_in_active_window_check.rpy +++ b/Monika After Story/game/dev/dev_in_active_window_check.rpy @@ -1,4 +1,3 @@ -rpy python 3 init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_kissing_test.rpy b/Monika After Story/game/dev/dev_kissing_test.rpy index 5498d2bdb5..8427c9f8b5 100644 --- a/Monika After Story/game/dev/dev_kissing_test.rpy +++ b/Monika After Story/game/dev/dev_kissing_test.rpy @@ -1,4 +1,3 @@ -rpy python 3 # test kiss transition init 5 python: diff --git a/Monika After Story/game/dev/dev_moods.rpy b/Monika After Story/game/dev/dev_moods.rpy index c56c1c7d71..b355f07992 100644 --- a/Monika After Story/game/dev/dev_moods.rpy +++ b/Monika After Story/game/dev/dev_moods.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## dev mode eggs #dev mode easter eggs diff --git a/Monika After Story/game/dev/dev_mouse_tracker.rpy b/Monika After Story/game/dev/dev_mouse_tracker.rpy index 128718f949..a9cab7367f 100644 --- a/Monika After Story/game/dev/dev_mouse_tracker.rpy +++ b/Monika After Story/game/dev/dev_mouse_tracker.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module that adds a tiny mouse tracker overlay init -1 python: diff --git a/Monika After Story/game/dev/dev_overlays.rpy b/Monika After Story/game/dev/dev_overlays.rpy index ef68f1c4b7..6f47f0e5cf 100644 --- a/Monika After Story/game/dev/dev_overlays.rpy +++ b/Monika After Story/game/dev/dev_overlays.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## overlay testing init 5 python: diff --git a/Monika After Story/game/dev/dev_pg_topics.rpy b/Monika After Story/game/dev/dev_pg_topics.rpy index 8294d81a0a..36df366903 100644 --- a/Monika After Story/game/dev/dev_pg_topics.rpy +++ b/Monika After Story/game/dev/dev_pg_topics.rpy @@ -1,4 +1,3 @@ -rpy python 3 # This file sets up special topics for testing the mas poem minigame # configuration diff --git a/Monika After Story/game/dev/dev_pong.rpy b/Monika After Story/game/dev/dev_pong.rpy index 6100732fed..553afd8333 100644 --- a/Monika After Story/game/dev/dev_pong.rpy +++ b/Monika After Story/game/dev/dev_pong.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## here we run some test cases init 999 python: diff --git a/Monika After Story/game/dev/dev_python.rpy b/Monika After Story/game/dev/dev_python.rpy index 59f6ca0d17..ca1956ae5c 100644 --- a/Monika After Story/game/dev/dev_python.rpy +++ b/Monika After Story/game/dev/dev_python.rpy @@ -1,4 +1,3 @@ -rpy python 3 # module for ptod related stuff (dev only init 10 python: diff --git a/Monika After Story/game/dev/dev_selector.rpy b/Monika After Story/game/dev/dev_selector.rpy index 2f7dee8653..859ec36a22 100644 --- a/Monika After Story/game/dev/dev_selector.rpy +++ b/Monika After Story/game/dev/dev_selector.rpy @@ -1,4 +1,3 @@ -rpy python 3 # selector testing init -100 python: diff --git a/Monika After Story/game/dev/dev_sprites.rpy b/Monika After Story/game/dev/dev_sprites.rpy index 1a0a38fa7d..687e548e02 100644 --- a/Monika After Story/game/dev/dev_sprites.rpy +++ b/Monika After Story/game/dev/dev_sprites.rpy @@ -1,4 +1,3 @@ -rpy python 3 # sprite testing code init 100 python: @@ -495,7 +494,7 @@ init -1 python: img_base, hl_base = self.gen_ims(bat_lvl) img_base = self.apply_filter(img_base) - + rv = renpy.Render(width, height) # start rendering @@ -552,7 +551,7 @@ init 5 python: ) label dev_dynamic_acs_test: - + m 6eua "now to test dynamic ACS" $ battery_level = 0 $ monika_chr.wear_acs(dev_acs_battery) @@ -574,7 +573,7 @@ label dev_dynamic_acs_test: m "5" $ battery_level = 6 m "6" - + m 6eua "now to take away acs" $ monika_chr.remove_acs(dev_acs_battery) diff --git a/Monika After Story/game/dev/dev_tools.rpy b/Monika After Story/game/dev/dev_tools.rpy index b9ab0718f9..c65780227b 100644 --- a/Monika After Story/game/dev/dev_tools.rpy +++ b/Monika After Story/game/dev/dev_tools.rpy @@ -1,4 +1,3 @@ -rpy python 3 # basic dev tool stuff init 800 python: diff --git a/Monika After Story/game/dev/dev_unittest.rpy b/Monika After Story/game/dev/dev_unittest.rpy index 63b070ffe3..741c03132d 100644 --- a/Monika After Story/game/dev/dev_unittest.rpy +++ b/Monika After Story/game/dev/dev_unittest.rpy @@ -1,4 +1,3 @@ -rpy python 3 # unit testing module # NOTE: no framework for now diff --git a/Monika After Story/game/dev/dev_weather.rpy b/Monika After Story/game/dev/dev_weather.rpy index 2abed3c614..724b52163f 100644 --- a/Monika After Story/game/dev/dev_weather.rpy +++ b/Monika After Story/game/dev/dev_weather.rpy @@ -1,4 +1,3 @@ -rpy python 3 # testin gweather init 5 python: # available only if moni affection is normal+ diff --git a/Monika After Story/game/dev/dev_xp.rpy b/Monika After Story/game/dev/dev_xp.rpy index c7304ff4d2..8cf11aca59 100644 --- a/Monika After Story/game/dev/dev_xp.rpy +++ b/Monika After Story/game/dev/dev_xp.rpy @@ -1,5 +1,3 @@ -rpy python 3 - init 5 python: addEvent( Event( diff --git a/Monika After Story/game/dev/dev_zoom_transition.rpy b/Monika After Story/game/dev/dev_zoom_transition.rpy index d43d8d298b..c5fe6d2399 100644 --- a/Monika After Story/game/dev/dev_zoom_transition.rpy +++ b/Monika After Story/game/dev/dev_zoom_transition.rpy @@ -1,4 +1,3 @@ -rpy python 3 # test zoom transitions init 5 python: addEvent( diff --git a/Monika After Story/game/dev/zz_dump.rpy b/Monika After Story/game/dev/zz_dump.rpy index 565505fe34..07678d02c3 100644 --- a/Monika After Story/game/dev/zz_dump.rpy +++ b/Monika After Story/game/dev/zz_dump.rpy @@ -1,4 +1,3 @@ -rpy python 3 # emergency dumping file init 999 python: diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 9ea8c98c1f..b163fd52c6 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that defines functions for story event handling # Assumes: # persistent.event_list diff --git a/Monika After Story/game/event-rules.rpy b/Monika After Story/game/event-rules.rpy index 18376f48d3..a63c44458d 100644 --- a/Monika After Story/game/event-rules.rpy +++ b/Monika After Story/game/event-rules.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that defines static classes used to create the rule tuples used in the # Event class. # The static classes are the ones used to manipulate the rule tuples diff --git a/Monika After Story/game/import_ddlc.rpy b/Monika After Story/game/import_ddlc.rpy index 4df86803c5..9c3c5d3850 100644 --- a/Monika After Story/game/import_ddlc.rpy +++ b/Monika After Story/game/import_ddlc.rpy @@ -1,4 +1,3 @@ -rpy python 3 # This file imports save data from DDLC without changing the original data. # By default, when this is run all relevant data from DDLC is imported. # Handling of individual variables can be handled by changing the settings below. diff --git a/Monika After Story/game/main_menu.rpy b/Monika After Story/game/main_menu.rpy index 54b76c44e9..91518b18a8 100644 --- a/Monika After Story/game/main_menu.rpy +++ b/Monika After Story/game/main_menu.rpy @@ -1,4 +1,3 @@ -rpy python 3 screen main_menu(): # This ensures that any other menu screen is replaced. diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index 6a43534694..1575344639 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -1,4 +1,3 @@ -rpy python 3 # pong difficulty changes on win / loss. Determines monika's paddle-movement-cap, the ball's start-speed, max-speed and acceleration. default persistent._mas_pong_difficulty = 10 # increases the pong difficulty for the next game by the value this is set to. Resets after a finished match. diff --git a/Monika After Story/game/progression.rpy b/Monika After Story/game/progression.rpy index 7a7cf7118d..00084e53e6 100644 --- a/Monika After Story/game/progression.rpy +++ b/Monika After Story/game/progression.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module that defines functions for handling game progression and leveling up diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 7df75da6a2..a96da7bb0a 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -1,4 +1,3 @@ -rpy python 3 init 100 python: layout.QUIT = store.mas_layout.QUIT layout.UNSTABLE = store.mas_layout.UNSTABLE diff --git a/Monika After Story/game/script-affection.rpy b/Monika After Story/game/script-affection.rpy index 15e166232a..a311378c17 100644 --- a/Monika After Story/game/script-affection.rpy +++ b/Monika After Story/game/script-affection.rpy @@ -1,4 +1,3 @@ -rpy python 3 # AFF010 is progpoints # # Affection module: diff --git a/Monika After Story/game/script-anniversary.rpy b/Monika After Story/game/script-anniversary.rpy index 8475a2b2c7..62660d30e3 100644 --- a/Monika After Story/game/script-anniversary.rpy +++ b/Monika After Story/game/script-anniversary.rpy @@ -1,4 +1,3 @@ -rpy python 3 init -2 python in mas_anni: import store import datetime diff --git a/Monika After Story/game/script-apologies.rpy b/Monika After Story/game/script-apologies.rpy index a10d703777..525436bb2e 100644 --- a/Monika After Story/game/script-apologies.rpy +++ b/Monika After Story/game/script-apologies.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Create an apology db for storing our times #Stores the event label as a key, its corresponding data is a tuple where: # [0] -> timedelta defined by: current total playtime + apology_active_expiry time diff --git a/Monika After Story/game/script-brbs.rpy b/Monika After Story/game/script-brbs.rpy index 7d000c5f98..8366ff146d 100644 --- a/Monika After Story/game/script-brbs.rpy +++ b/Monika After Story/game/script-brbs.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## This script file holds all of the brb topics # Some conventions: # - All brbs should have their markSeen set to True so they don't show up in unseen diff --git a/Monika After Story/game/script-compliments.rpy b/Monika After Story/game/script-compliments.rpy index 9fc14e9182..354a3a92dd 100644 --- a/Monika After Story/game/script-compliments.rpy +++ b/Monika After Story/game/script-compliments.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Module for complimenting Monika # # Compliments work by using the "unlocked" logic. diff --git a/Monika After Story/game/script-easter-eggs.rpy b/Monika After Story/game/script-easter-eggs.rpy index 4172f85281..1f1b5b21b5 100644 --- a/Monika After Story/game/script-easter-eggs.rpy +++ b/Monika After Story/game/script-easter-eggs.rpy @@ -1,4 +1,3 @@ -rpy python 3 # script stuff that is actually easter eggs # sayori music chnage/scare diff --git a/Monika After Story/game/script-farewells.rpy b/Monika After Story/game/script-farewells.rpy index 2f1533710c..af087d41ce 100644 --- a/Monika After Story/game/script-farewells.rpy +++ b/Monika After Story/game/script-farewells.rpy @@ -1,4 +1,3 @@ -rpy python 3 ##This file contains all of the variations of goodbye that monika can give. ## This also contains a store with a utility function to select an appropriate ## farewell diff --git a/Monika After Story/game/script-fun-facts.rpy b/Monika After Story/game/script-fun-facts.rpy index f4e4d8fb9b..196d2fd60d 100644 --- a/Monika After Story/game/script-fun-facts.rpy +++ b/Monika After Story/game/script-fun-facts.rpy @@ -1,4 +1,3 @@ -rpy python 3 #Persistent event database for fun facts default persistent._mas_fun_facts_database = dict() diff --git a/Monika After Story/game/script-grammar.rpy b/Monika After Story/game/script-grammar.rpy index 3aed40d81a..401ff39775 100644 --- a/Monika After Story/game/script-grammar.rpy +++ b/Monika After Story/game/script-grammar.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Monika's Grammar Tip of the Day (GTOD) # TIPS # 0 - Intro diff --git a/Monika After Story/game/script-greetings.rpy b/Monika After Story/game/script-greetings.rpy index 2cb648a600..51db4ecabb 100644 --- a/Monika After Story/game/script-greetings.rpy +++ b/Monika After Story/game/script-greetings.rpy @@ -1,4 +1,3 @@ -rpy python 3 ##This page holds all of the random greetings that Monika can give you after you've gone through all of her "reload" scripts #Make a list of every label that starts with "greeting_", and use that for random greetings during startup diff --git a/Monika After Story/game/script-holidays.rpy b/Monika After Story/game/script-holidays.rpy index 6e353318d7..2711d8e6ae 100644 --- a/Monika After Story/game/script-holidays.rpy +++ b/Monika After Story/game/script-holidays.rpy @@ -1,4 +1,3 @@ -rpy python 3 ## holiday info goes here # # TOC diff --git a/Monika After Story/game/script-introduction.rpy b/Monika After Story/game/script-introduction.rpy index 266b37f584..6a57ccc56d 100644 --- a/Monika After Story/game/script-introduction.rpy +++ b/Monika After Story/game/script-introduction.rpy @@ -1,4 +1,3 @@ -rpy python 3 init -1 python: import store.mas_affection as mas_aff label introduction: diff --git a/Monika After Story/game/script-islands-event.rpy b/Monika After Story/game/script-islands-event.rpy index e81c8a8c40..7f4391a2e2 100644 --- a/Monika After Story/game/script-islands-event.rpy +++ b/Monika After Story/game/script-islands-event.rpy @@ -1,4 +1,3 @@ -rpy python 3 # Monika's ???? Event # deserves it's own file because of how much dialogue these have # it basically shows a new screen over everything, and has an image map From 416c011d8ec98e3e4d16701f4a349cdb25b642be Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 10:34:37 -0500 Subject: [PATCH 041/180] rm this config var --- Monika After Story/game/definitions.rpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 0d0c30a4eb..3eabad5d3a 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -5,8 +5,8 @@ define config.developer = False ###R7+ Config Var adjustments ##7.3.3 -#Only devs need this -define config.report_extraneous_attributes = False +#Only devs need this, NOTE: This was removed in r8 +#define config.report_extraneous_attributes = False ##7.3.0 define config.keyword_after_python = True ##7.1.1 From e0cb975805546137dcbb69d12f2e22b7e03e439a Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 10:37:53 -0500 Subject: [PATCH 042/180] more removal --- Monika After Story/game/definitions.rpy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 3eabad5d3a..03ff479bf7 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -7,8 +7,10 @@ define config.developer = False ##7.3.3 #Only devs need this, NOTE: This was removed in r8 #define config.report_extraneous_attributes = False -##7.3.0 -define config.keyword_after_python = True + +##7.3.0, NOTE: This was removed in r8 +#define config.keyword_after_python = True + ##7.1.1 #Fix menu textbox issues define config.menu_showed_window = True @@ -16,9 +18,11 @@ define config.menu_showed_window = True define config.window_auto_show = ["say"] #Fix textbox flickering define config.window_auto_hide = ["scene", "call screen"] + ##7.0 #Fixes spaceroom masks from restarting every interaction define config.replay_movie_sprites = False + ##6.99.13 define config.atl_one_frame = False From 4562c03ec115297f1d9e25dfbb0e41536e260fab Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 10:53:36 -0500 Subject: [PATCH 043/180] init 999? --- Monika After Story/game/definitions.rpy | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 03ff479bf7..7544e7e5cb 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -4,20 +4,21 @@ define config.developer = False # define persistent.steam = "steamapps" in config.basedir.lower() ###R7+ Config Var adjustments -##7.3.3 -#Only devs need this, NOTE: This was removed in r8 -#define config.report_extraneous_attributes = False - -##7.3.0, NOTE: This was removed in r8 -#define config.keyword_after_python = True - -##7.1.1 -#Fix menu textbox issues -define config.menu_showed_window = True -#Fix textbox sometimes disappearing -define config.window_auto_show = ["say"] -#Fix textbox flickering -define config.window_auto_hide = ["scene", "call screen"] +init 999: + ##7.3.3 + #Only devs need this, NOTE: This was removed in r8 + define config.report_extraneous_attributes = False + + ##7.3.0, NOTE: This was removed in r8 + define config.keyword_after_python = True + + ##7.1.1 + #Fix menu textbox issues + define config.menu_showed_window = True + #Fix textbox sometimes disappearing + define config.window_auto_show = ["say"] + #Fix textbox flickering + define config.window_auto_hide = ["scene", "call screen"] ##7.0 #Fixes spaceroom masks from restarting every interaction From 742d8ffab8118be7d4edef32972f3d9c2d94738a Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 14 Feb 2022 10:57:30 -0500 Subject: [PATCH 044/180] actually move all of them this time --- Monika After Story/game/definitions.rpy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 7544e7e5cb..0c5792f4d6 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -6,10 +6,10 @@ define config.developer = False ###R7+ Config Var adjustments init 999: ##7.3.3 - #Only devs need this, NOTE: This was removed in r8 + #Only devs need this define config.report_extraneous_attributes = False - ##7.3.0, NOTE: This was removed in r8 + ##7.3.0 define config.keyword_after_python = True ##7.1.1 @@ -20,12 +20,12 @@ init 999: #Fix textbox flickering define config.window_auto_hide = ["scene", "call screen"] -##7.0 -#Fixes spaceroom masks from restarting every interaction -define config.replay_movie_sprites = False + ##7.0 + #Fixes spaceroom masks from restarting every interaction + define config.replay_movie_sprites = False -##6.99.13 -define config.atl_one_frame = False + ##6.99.13 + define config.atl_one_frame = False python early: From e9e2d4bb7c8238bf8fe53d4e63ccad1afe1542d3 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 21:58:57 -0500 Subject: [PATCH 045/180] test --- Monika After Story/game/0config.rpy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index ae3481f5ce..454858b780 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -114,6 +114,9 @@ init -1200 python: renpy.config.gl_test_image = "white" ################START: INIT TIME CONFIGS +init -999 python: + #DEBUG: test w/o a config var lock + config.locked = False ## Uncomment the following line to set an audio file that will be played while ## the player is at the main menu. This file will continue playing into the From 8dade46204db886d99592db2f625c83915f297ca Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:19:17 -0500 Subject: [PATCH 046/180] no more SDK caching. Bad --- .github/workflows/mas_check.yml | 53 +++++++++------------------------ 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 2a443fb332..6209ba00e8 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -6,6 +6,7 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the master branch push: + branches: [ master, content, unstable ] pull_request: branches: [ content ] @@ -16,7 +17,7 @@ on: jobs: # This workflow contains a single job called "build" build: - name: ci_build + name: build deps and lint # The type of runner that the job will run on runs-on: ubuntu-latest @@ -34,66 +35,40 @@ jobs: with: # Version range or exact version of a Python version to use, using SemVer's version range syntax. python-version: 2.7.18 # optional, default is 3.x + # The target architecture (x86, x64) of the Python interpreter. + architecture: x86 # optional - # get/dl renpy src - - name: cache rpy source - id: cache-rpy - uses: actions/cache@v2 - with: - path: renpy/ - key: ${{ runner.os }}-rpy - + # dl renpy src - name: Download rpy source - if: steps.cache-rpy.outputs.cache-hit != 'true' run: | - renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') + renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=")..tar.bz2(?=".)') wget https://nightly.renpy.org/current/$renpysdk tar xf $renpysdk rm $renpysdk mv ${renpysdk/.tar.bz2/} renpy - # get/download base mas - - name: cache base MAS - id: cache-mas - uses: actions/cache@v2 - with: - path: mas0105/ - key: ${{ runner.os }}-mas - + # download base mas - name: Download base MAS - if: steps.cache-mas.outputs.cache-hit != 'true' run: | wget https://s3-us-west-2.amazonaws.com/monika-after-story/ddlc/mas.zip mkdir mas0105 unzip mas.zip -d mas0105 - # TEMP - # - name: what are these - # run: | - # file mas0105 - # file renpy - - # copy files over - - name: copy source over + # copy over gh files to base + - name: copy gh files to base run: cp -Rf Monika\ After\ Story/* mas0105/ - # touch file for unstable so it doesn't raise exceptions for some things - - name: exception skip for unstable - if: github.ref == 'refs/heads/unstable' - run: touch mas0105/trb - # run sprite checkers - name: check sprites - run: python tools/ghactions.py + run: python tools/travis.py # lint renpy - name: rpy lint run: | - cd renpy - ./renpy.sh "../mas0105/" lint | grep -E -v "^$|Could not find image \(monika [0-9][^[:space:]]+ corresponding to attributes on say statement\.|'monika [0-9][^[:space:]]+' is not an image\.|The image named 'monika [0-9][^[:space:]]+ was not declared\." + ./renpy/renpy.sh "mas0105/" lint > renpy_output + python tools/renpy_lint_parser.py + cat renpy_output_clean # distribute - name: rpy distribute - run: | - cd renpy - ./renpy.sh launcher distribute "../mas0105/" --package Mod + run: ./renpy/renpy.sh launcher distribute "mas0105/" From 3ccc36c06e4855318cce53adb52f21dfa4e092fb Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:19:51 -0500 Subject: [PATCH 047/180] remove unneeded config --- Monika After Story/game/0config.rpy | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index 454858b780..7a8a7fe473 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -114,10 +114,6 @@ init -1200 python: renpy.config.gl_test_image = "white" ################START: INIT TIME CONFIGS -init -999 python: - #DEBUG: test w/o a config var lock - config.locked = False - ## Uncomment the following line to set an audio file that will be played while ## the player is at the main menu. This file will continue playing into the ## game, until it is stopped or another file is played. From df57edd4149eddf6c1e1c80f85c525312d6a39ab Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:21:46 -0500 Subject: [PATCH 048/180] let it run --- .github/workflows/mas_check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 6209ba00e8..8daffc8ffe 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -6,7 +6,6 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master, content, unstable ] pull_request: branches: [ content ] From dc3d8d987fe203e62efa5210263ac0e2b68ba9eb Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:31:08 -0500 Subject: [PATCH 049/180] mix the stuff properly --- .github/workflows/mas_check.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 8daffc8ffe..ea30ef7af2 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -16,7 +16,7 @@ on: jobs: # This workflow contains a single job called "build" build: - name: build deps and lint + name: ci_build # The type of runner that the job will run on runs-on: ubuntu-latest @@ -34,8 +34,6 @@ jobs: with: # Version range or exact version of a Python version to use, using SemVer's version range syntax. python-version: 2.7.18 # optional, default is 3.x - # The target architecture (x86, x64) of the Python interpreter. - architecture: x86 # optional # dl renpy src - name: Download rpy source @@ -46,28 +44,37 @@ jobs: rm $renpysdk mv ${renpysdk/.tar.bz2/} renpy - # download base mas + # get/download base mas + - name: cache base MAS + id: cache-mas + uses: actions/cache@v2 + with: + path: mas0105/ + key: ${{ runner.os }}-mas + - name: Download base MAS + if: steps.cache-mas.outputs.cache-hit != 'true' run: | wget https://s3-us-west-2.amazonaws.com/monika-after-story/ddlc/mas.zip mkdir mas0105 unzip mas.zip -d mas0105 # copy over gh files to base - - name: copy gh files to base + - name: copy source over run: cp -Rf Monika\ After\ Story/* mas0105/ # run sprite checkers - name: check sprites - run: python tools/travis.py + run: python tools/ghactions.py # lint renpy - name: rpy lint run: | - ./renpy/renpy.sh "mas0105/" lint > renpy_output - python tools/renpy_lint_parser.py - cat renpy_output_clean + cd renpy + ./renpy.sh "../mas0105/" lint | grep -E -v "^$|Could not find image \(monika [0-9][^[:space:]]+ corresponding to attributes on say statement\.|'monika [0-9][^[:space:]]+' is not an image\.|The image named 'monika [0-9][^[:space:]]+ was not declared\." # distribute - name: rpy distribute - run: ./renpy/renpy.sh launcher distribute "mas0105/" + run: | + cd renpy + ./renpy.sh launcher distribute "../mas0105/" --package Mod From ba1577f0157c3309d21c8bf8d7a9b151668022b7 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:40:55 -0500 Subject: [PATCH 050/180] oops --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index ea30ef7af2..b33d3a028d 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -38,7 +38,7 @@ jobs: # dl renpy src - name: Download rpy source run: | - renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=")..tar.bz2(?=".)') + renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') wget https://nightly.renpy.org/current/$renpysdk tar xf $renpysdk rm $renpysdk From b29ebdce75f8ee2d4685aa826169ac4a0b8235ad Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:46:28 -0500 Subject: [PATCH 051/180] actually download from the right URL --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index b33d3a028d..96d1b30a25 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -39,7 +39,7 @@ jobs: - name: Download rpy source run: | renpysdk=$(wget -qO- https://nightly.renpy.org/current-8/index.html | grep -P -m 1 -o '(?<=href=").*\.tar\.bz2(?=".*)') - wget https://nightly.renpy.org/current/$renpysdk + wget https://nightly.renpy.org/current-8/$renpysdk tar xf $renpysdk rm $renpysdk mv ${renpysdk/.tar.bz2/} renpy From 7ec50aba41e08ac67b22f3808e8e8a4d5778f736 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 22:50:52 -0500 Subject: [PATCH 052/180] cPickle -> pickle --- Monika After Story/game/dev/dev_db.rpy | 4 ++-- Monika After Story/game/import_ddlc.rpy | 2 +- Monika After Story/game/zz_backup.rpy | 4 ++-- Monika After Story/game/zz_dockingstation.rpy | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Monika After Story/game/dev/dev_db.rpy b/Monika After Story/game/dev/dev_db.rpy index 1671ef8523..b87dcfd115 100644 --- a/Monika After Story/game/dev/dev_db.rpy +++ b/Monika After Story/game/dev/dev_db.rpy @@ -215,7 +215,7 @@ init python: init python in dev_mas_shared: - import cPickle + import pickle import store import store.mas_ev_data_ver as ver @@ -257,7 +257,7 @@ init python in dev_mas_shared: # select persistent to load if self.in_char: pkg = store.mas_docking_station.getPackage("persistent") - pdata = cPickle.loads(pkg.read().decode("zlib")) + pdata = pickle.loads(pkg.read().decode("zlib")) pkg.close() else: pdata = store.persistent diff --git a/Monika After Story/game/import_ddlc.rpy b/Monika After Story/game/import_ddlc.rpy index 9c3c5d3850..1b1cf9d468 100644 --- a/Monika After Story/game/import_ddlc.rpy +++ b/Monika After Story/game/import_ddlc.rpy @@ -104,7 +104,7 @@ label import_ddlc_persistent: ddlc_persistent = None try: with open(ddlc_save_path, "rb") as ddlc_pfile: - ddlc_persistent = mas_dockstat.cPickle.loads(ddlc_pfile.read().decode("zlib")) + ddlc_persistent = mas_dockstat.pickle.loads(ddlc_pfile.read().decode("zlib")) except Exception as e: store.mas_utils.mas_log.error("Failed to read/decode DDLC persistent: {0}".format(e)) diff --git a/Monika After Story/game/zz_backup.rpy b/Monika After Story/game/zz_backup.rpy index a3e2872d5a..a35b6b73a3 100644 --- a/Monika After Story/game/zz_backup.rpy +++ b/Monika After Story/game/zz_backup.rpy @@ -19,7 +19,7 @@ default persistent._mas_incompat_per_entered = False python early in mas_per_check: import __main__ - import cPickle + import pickle import os import datetime import shutil @@ -109,7 +109,7 @@ python early in mas_per_check: per_file = file(_tp_persistent, "rb") per_data = per_file.read().decode("zlib") per_file.close() - actual_data = cPickle.loads(per_data) + actual_data = pickle.loads(per_data) if get_data: return True, actual_data diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 2123cae88d..19dc663b3d 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -1179,7 +1179,7 @@ init -11 python in mas_dockstat: init python in mas_dockstat: import store - import cPickle + import pickle # previous vars dict previous_vars = dict() @@ -1300,7 +1300,7 @@ init 200 python in mas_dockstat: END_DELIM = "|||per|" try: - _outbuffer.write(codecs.encode(cPickle.dumps(store.persistent), "base64")) + _outbuffer.write(codecs.encode(pickle.dumps(store.persistent), "base64")) _outbuffer.write(END_DELIM) return True @@ -1849,8 +1849,8 @@ init 200 python in mas_dockstat: # TODO: change separator to a very large delimeter so we can handle persistents larger than 4MB splitted = data_line.split("|||per|") if(len(splitted)>0): - return cPickle.loads(codecs.decode(splitted[0] + b'='*4, "base64")) - return cPickle.loads(codecs.decode(data_line + b'='*4, "base64")) + return pickle.loads(codecs.decode(splitted[0] + b'='*4, "base64")) + return pickle.loads(codecs.decode(data_line + b'='*4, "base64")) except Exception as e: log.error( From 5f3efdfb56733f89f57c1cb9de4d160ef8408630 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 23:02:23 -0500 Subject: [PATCH 053/180] try deleting logging from py pack --- .../game/python-packages/logging/__init__.py | 1751 ----------------- .../game/python-packages/logging/config.py | 919 --------- .../game/python-packages/logging/handlers.py | 1242 ------------ 3 files changed, 3912 deletions(-) delete mode 100644 Monika After Story/game/python-packages/logging/__init__.py delete mode 100644 Monika After Story/game/python-packages/logging/config.py delete mode 100644 Monika After Story/game/python-packages/logging/handlers.py diff --git a/Monika After Story/game/python-packages/logging/__init__.py b/Monika After Story/game/python-packages/logging/__init__.py deleted file mode 100644 index 88fb84cd71..0000000000 --- a/Monika After Story/game/python-packages/logging/__init__.py +++ /dev/null @@ -1,1751 +0,0 @@ -# Copyright 2001-2014 by Vinay Sajip. All Rights Reserved. -# -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appear in all copies and that -# both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of Vinay Sajip -# not be used in advertising or publicity pertaining to distribution -# of the software without specific, written prior permission. -# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING -# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL -# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR -# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER -# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -""" -Logging package for Python. Based on PEP 282 and comments thereto in -comp.lang.python. - -Copyright (C) 2001-2014 Vinay Sajip. All Rights Reserved. - -To use, simply 'import logging' and log away! -""" - -import sys, os, time, cStringIO, traceback, warnings, weakref, collections - -__all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', - 'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO', - 'LogRecord', 'Logger', 'LoggerAdapter', 'NOTSET', 'NullHandler', - 'StreamHandler', 'WARN', 'WARNING', 'addLevelName', 'basicConfig', - 'captureWarnings', 'critical', 'debug', 'disable', 'error', - 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', - 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning'] - -try: - import codecs -except ImportError: - codecs = None - -try: - import thread - import threading -except ImportError: - thread = None - -__author__ = "Vinay Sajip " -__status__ = "production" -# Note: the attributes below are no longer maintained. -__version__ = "0.5.1.2" -__date__ = "07 February 2010" - -#--------------------------------------------------------------------------- -# Miscellaneous module data -#--------------------------------------------------------------------------- -try: - unicode - _unicode = True -except NameError: - _unicode = False - -# next bit filched from 1.5.2's inspect.py -def currentframe(): - """Return the frame object for the caller's stack frame.""" - try: - raise Exception - except: - return sys.exc_info()[2].tb_frame.f_back - -if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3) -# done filching - -# -# _srcfile is used when walking the stack to check when we've got the first -# caller stack frame. -# -_srcfile = os.path.normcase(currentframe.__code__.co_filename) - -# _srcfile is only used in conjunction with sys._getframe(). -# To provide compatibility with older versions of Python, set _srcfile -# to None if _getframe() is not available; this value will prevent -# findCaller() from being called. -#if not hasattr(sys, "_getframe"): -# _srcfile = None - -# -#_startTime is used as the base when calculating the relative time of events -# -_startTime = time.time() - -# -#raiseExceptions is used to see if exceptions during handling should be -#propagated -# -raiseExceptions = 1 - -# -# If you don't want threading information in the log, set this to zero -# -logThreads = 1 - -# -# If you don't want multiprocessing information in the log, set this to zero -# -logMultiprocessing = 1 - -# -# If you don't want process information in the log, set this to zero -# -logProcesses = 1 - -#--------------------------------------------------------------------------- -# Level related stuff -#--------------------------------------------------------------------------- -# -# Default levels and level names, these can be replaced with any positive set -# of values having corresponding names. There is a pseudo-level, NOTSET, which -# is only really there as a lower limit for user-defined levels. Handlers and -# loggers are initialized with NOTSET so that they will log all messages, even -# at user-defined levels. -# - -CRITICAL = 50 -FATAL = CRITICAL -ERROR = 40 -WARNING = 30 -WARN = WARNING -INFO = 20 -DEBUG = 10 -NOTSET = 0 - -_levelNames = { - CRITICAL : 'CRITICAL', - ERROR : 'ERROR', - WARNING : 'WARNING', - INFO : 'INFO', - DEBUG : 'DEBUG', - NOTSET : 'NOTSET', - 'CRITICAL' : CRITICAL, - 'ERROR' : ERROR, - 'WARN' : WARNING, - 'WARNING' : WARNING, - 'INFO' : INFO, - 'DEBUG' : DEBUG, - 'NOTSET' : NOTSET, -} - -def getLevelName(level): - """ - Return the textual representation of logging level 'level'. - - If the level is one of the predefined levels (CRITICAL, ERROR, WARNING, - INFO, DEBUG) then you get the corresponding string. If you have - associated levels with names using addLevelName then the name you have - associated with 'level' is returned. - - If a numeric value corresponding to one of the defined levels is passed - in, the corresponding string representation is returned. - - Otherwise, the string "Level %s" % level is returned. - """ - return _levelNames.get(level, ("Level %s" % level)) - -def addLevelName(level, levelName): - """ - Associate 'levelName' with 'level'. - - This is used when converting levels to text during message formatting. - """ - _acquireLock() - try: #unlikely to cause an exception, but you never know... - _levelNames[level] = levelName - _levelNames[levelName] = level - finally: - _releaseLock() - -def _checkLevel(level): - if isinstance(level, (int, long)): - rv = level - elif str(level) == level: - if level not in _levelNames: - raise ValueError("Unknown level: %r" % level) - rv = _levelNames[level] - else: - raise TypeError("Level not an integer or a valid string: %r" % level) - return rv - -#--------------------------------------------------------------------------- -# Thread-related stuff -#--------------------------------------------------------------------------- - -# -#_lock is used to serialize access to shared data structures in this module. -#This needs to be an RLock because fileConfig() creates and configures -#Handlers, and so might arbitrary user threads. Since Handler code updates the -#shared dictionary _handlers, it needs to acquire the lock. But if configuring, -#the lock would already have been acquired - so we need an RLock. -#The same argument applies to Loggers and Manager.loggerDict. -# -if thread: - _lock = threading.RLock() -else: - _lock = None - -def _acquireLock(): - """ - Acquire the module-level lock for serializing access to shared data. - - This should be released with _releaseLock(). - """ - if _lock: - _lock.acquire() - -def _releaseLock(): - """ - Release the module-level lock acquired by calling _acquireLock(). - """ - if _lock: - _lock.release() - -#--------------------------------------------------------------------------- -# The logging record -#--------------------------------------------------------------------------- - -class LogRecord(object): - """ - A LogRecord instance represents an event being logged. - - LogRecord instances are created every time something is logged. They - contain all the information pertinent to the event being logged. The - main information passed in is in msg and args, which are combined - using str(msg) % args to create the message field of the record. The - record also includes information such as when the record was created, - the source line where the logging call was made, and any exception - information to be logged. - """ - def __init__(self, name, level, pathname, lineno, - msg, args, exc_info, func=None): - """ - Initialize a logging record with interesting information. - """ - ct = time.time() - self.name = name - self.msg = msg - # - # The following statement allows passing of a dictionary as a sole - # argument, so that you can do something like - # logging.debug("a %(a)d b %(b)s", {'a':1, 'b':2}) - # Suggested by Stefan Behnel. - # Note that without the test for args[0], we get a problem because - # during formatting, we test to see if the arg is present using - # 'if self.args:'. If the event being logged is e.g. 'Value is %d' - # and if the passed arg fails 'if self.args:' then no formatting - # is done. For example, logger.warn('Value is %d', 0) would log - # 'Value is %d' instead of 'Value is 0'. - # For the use case of passing a dictionary, this should not be a - # problem. - # Issue #21172: a request was made to relax the isinstance check - # to hasattr(args[0], '__getitem__'). However, the docs on string - # formatting still seem to suggest a mapping object is required. - # Thus, while not removing the isinstance check, it does now look - # for collections.Mapping rather than, as before, dict. - if (args and len(args) == 1 and isinstance(args[0], collections.Mapping) - and args[0]): - args = args[0] - self.args = args - self.levelname = getLevelName(level) - self.levelno = level - self.pathname = pathname - try: - self.filename = os.path.basename(pathname) - self.module = os.path.splitext(self.filename)[0] - except (TypeError, ValueError, AttributeError): - self.filename = pathname - self.module = "Unknown module" - self.exc_info = exc_info - self.exc_text = None # used to cache the traceback text - self.lineno = lineno - self.funcName = func - self.created = ct - self.msecs = (ct - long(ct)) * 1000 - self.relativeCreated = (self.created - _startTime) * 1000 - if logThreads and thread: - self.thread = thread.get_ident() - self.threadName = threading.current_thread().name - else: - self.thread = None - self.threadName = None - if not logMultiprocessing: - self.processName = None - else: - self.processName = 'MainProcess' - mp = sys.modules.get('multiprocessing') - if mp is not None: - # Errors may occur if multiprocessing has not finished loading - # yet - e.g. if a custom import hook causes third-party code - # to run when multiprocessing calls import. See issue 8200 - # for an example - try: - self.processName = mp.current_process().name - except StandardError: - pass - if logProcesses and hasattr(os, 'getpid'): - self.process = os.getpid() - else: - self.process = None - - def __str__(self): - return ''%(self.name, self.levelno, - self.pathname, self.lineno, self.msg) - - def getMessage(self): - """ - Return the message for this LogRecord. - - Return the message for this LogRecord after merging any user-supplied - arguments with the message. - """ - if not _unicode: #if no unicode support... - msg = str(self.msg) - else: - msg = self.msg - if not isinstance(msg, basestring): - try: - msg = str(self.msg) - except UnicodeError: - msg = self.msg #Defer encoding till later - if self.args: - msg = msg % self.args - return msg - -def makeLogRecord(dict): - """ - Make a LogRecord whose attributes are defined by the specified dictionary, - This function is useful for converting a logging event received over - a socket connection (which is sent as a dictionary) into a LogRecord - instance. - """ - rv = LogRecord(None, None, "", 0, "", (), None, None) - rv.__dict__.update(dict) - return rv - -#--------------------------------------------------------------------------- -# Formatter classes and functions -#--------------------------------------------------------------------------- - -class Formatter(object): - """ - Formatter instances are used to convert a LogRecord to text. - - Formatters need to know how a LogRecord is constructed. They are - responsible for converting a LogRecord to (usually) a string which can - be interpreted by either a human or an external system. The base Formatter - allows a formatting string to be specified. If none is supplied, the - default value of "%s(message)\\n" is used. - - The Formatter can be initialized with a format string which makes use of - knowledge of the LogRecord attributes - e.g. the default value mentioned - above makes use of the fact that the user's message and arguments are pre- - formatted into a LogRecord's message attribute. Currently, the useful - attributes in a LogRecord are described by: - - %(name)s Name of the logger (logging channel) - %(levelno)s Numeric logging level for the message (DEBUG, INFO, - WARNING, ERROR, CRITICAL) - %(levelname)s Text logging level for the message ("DEBUG", "INFO", - "WARNING", "ERROR", "CRITICAL") - %(pathname)s Full pathname of the source file where the logging - call was issued (if available) - %(filename)s Filename portion of pathname - %(module)s Module (name portion of filename) - %(lineno)d Source line number where the logging call was issued - (if available) - %(funcName)s Function name - %(created)f Time when the LogRecord was created (time.time() - return value) - %(asctime)s Textual time when the LogRecord was created - %(msecs)d Millisecond portion of the creation time - %(relativeCreated)d Time in milliseconds when the LogRecord was created, - relative to the time the logging module was loaded - (typically at application startup time) - %(thread)d Thread ID (if available) - %(threadName)s Thread name (if available) - %(process)d Process ID (if available) - %(message)s The result of record.getMessage(), computed just as - the record is emitted - """ - - converter = time.localtime - - def __init__(self, fmt=None, datefmt=None): - """ - Initialize the formatter with specified format strings. - - Initialize the formatter either with the specified format string, or a - default as described above. Allow for specialized date formatting with - the optional datefmt argument (if omitted, you get the ISO8601 format). - """ - if fmt: - self._fmt = fmt - else: - self._fmt = "%(message)s" - self.datefmt = datefmt - - def formatTime(self, record, datefmt=None): - """ - Return the creation time of the specified LogRecord as formatted text. - - This method should be called from format() by a formatter which - wants to make use of a formatted time. This method can be overridden - in formatters to provide for any specific requirement, but the - basic behaviour is as follows: if datefmt (a string) is specified, - it is used with time.strftime() to format the creation time of the - record. Otherwise, the ISO8601 format is used. The resulting - string is returned. This function uses a user-configurable function - to convert the creation time to a tuple. By default, time.localtime() - is used; to change this for a particular formatter instance, set the - 'converter' attribute to a function with the same signature as - time.localtime() or time.gmtime(). To change it for all formatters, - for example if you want all logging times to be shown in GMT, - set the 'converter' attribute in the Formatter class. - """ - ct = self.converter(record.created) - if datefmt: - s = time.strftime(datefmt, ct) - else: - t = time.strftime("%Y-%m-%d %H:%M:%S", ct) - s = "%s,%03d" % (t, record.msecs) - return s - - def formatException(self, ei): - """ - Format and return the specified exception information as a string. - - This default implementation just uses - traceback.print_exception() - """ - sio = cStringIO.StringIO() - traceback.print_exception(ei[0], ei[1], ei[2], None, sio) - s = sio.getvalue() - sio.close() - if s[-1:] == "\n": - s = s[:-1] - return s - - def usesTime(self): - """ - Check if the format uses the creation time of the record. - """ - return self._fmt.find("%(asctime)") >= 0 - - def format(self, record): - """ - Format the specified record as text. - - The record's attribute dictionary is used as the operand to a - string formatting operation which yields the returned string. - Before formatting the dictionary, a couple of preparatory steps - are carried out. The message attribute of the record is computed - using LogRecord.getMessage(). If the formatting string uses the - time (as determined by a call to usesTime(), formatTime() is - called to format the event time. If there is exception information, - it is formatted using formatException() and appended to the message. - """ - record.message = record.getMessage() - if self.usesTime(): - record.asctime = self.formatTime(record, self.datefmt) - try: - s = self._fmt % record.__dict__ - except UnicodeDecodeError as e: - # Issue 25664. The logger name may be Unicode. Try again ... - try: - record.name = record.name.decode('utf-8') - s = self._fmt % record.__dict__ - except UnicodeDecodeError: - raise e - if record.exc_info: - # Cache the traceback text to avoid converting it multiple times - # (it's constant anyway) - if not record.exc_text: - record.exc_text = self.formatException(record.exc_info) - if record.exc_text: - if s[-1:] != "\n": - s = s + "\n" - try: - s = s + record.exc_text - except UnicodeError: - # Sometimes filenames have non-ASCII chars, which can lead - # to errors when s is Unicode and record.exc_text is str - # See issue 8924. - # We also use replace for when there are multiple - # encodings, e.g. UTF-8 for the filesystem and latin-1 - # for a script. See issue 13232. - s = s + record.exc_text.decode(sys.getfilesystemencoding(), - 'replace') - return s - -# -# The default formatter to use when no other is specified -# -_defaultFormatter = Formatter() - -class BufferingFormatter(object): - """ - A formatter suitable for formatting a number of records. - """ - def __init__(self, linefmt=None): - """ - Optionally specify a formatter which will be used to format each - individual record. - """ - if linefmt: - self.linefmt = linefmt - else: - self.linefmt = _defaultFormatter - - def formatHeader(self, records): - """ - Return the header string for the specified records. - """ - return "" - - def formatFooter(self, records): - """ - Return the footer string for the specified records. - """ - return "" - - def format(self, records): - """ - Format the specified records and return the result as a string. - """ - rv = "" - if len(records) > 0: - rv = rv + self.formatHeader(records) - for record in records: - rv = rv + self.linefmt.format(record) - rv = rv + self.formatFooter(records) - return rv - -#--------------------------------------------------------------------------- -# Filter classes and functions -#--------------------------------------------------------------------------- - -class Filter(object): - """ - Filter instances are used to perform arbitrary filtering of LogRecords. - - Loggers and Handlers can optionally use Filter instances to filter - records as desired. The base filter class only allows events which are - below a certain point in the logger hierarchy. For example, a filter - initialized with "A.B" will allow events logged by loggers "A.B", - "A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If - initialized with the empty string, all events are passed. - """ - def __init__(self, name=''): - """ - Initialize a filter. - - Initialize with the name of the logger which, together with its - children, will have its events allowed through the filter. If no - name is specified, allow every event. - """ - self.name = name - self.nlen = len(name) - - def filter(self, record): - """ - Determine if the specified record is to be logged. - - Is the specified record to be logged? Returns 0 for no, nonzero for - yes. If deemed appropriate, the record may be modified in-place. - """ - if self.nlen == 0: - return 1 - elif self.name == record.name: - return 1 - elif record.name.find(self.name, 0, self.nlen) != 0: - return 0 - return (record.name[self.nlen] == ".") - -class Filterer(object): - """ - A base class for loggers and handlers which allows them to share - common code. - """ - def __init__(self): - """ - Initialize the list of filters to be an empty list. - """ - self.filters = [] - - def addFilter(self, filter): - """ - Add the specified filter to this handler. - """ - if not (filter in self.filters): - self.filters.append(filter) - - def removeFilter(self, filter): - """ - Remove the specified filter from this handler. - """ - if filter in self.filters: - self.filters.remove(filter) - - def filter(self, record): - """ - Determine if a record is loggable by consulting all the filters. - - The default is to allow the record to be logged; any filter can veto - this and the record is then dropped. Returns a zero value if a record - is to be dropped, else non-zero. - """ - rv = 1 - for f in self.filters: - if not f.filter(record): - rv = 0 - break - return rv - -#--------------------------------------------------------------------------- -# Handler classes and functions -#--------------------------------------------------------------------------- - -_handlers = weakref.WeakValueDictionary() #map of handler names to handlers -_handlerList = [] # added to allow handlers to be removed in reverse of order initialized - -def _removeHandlerRef(wr): - """ - Remove a handler reference from the internal cleanup list. - """ - # This function can be called during module teardown, when globals are - # set to None. It can also be called from another thread. So we need to - # pre-emptively grab the necessary globals and check if they're None, - # to prevent race conditions and failures during interpreter shutdown. - acquire, release, handlers = _acquireLock, _releaseLock, _handlerList - if acquire and release and handlers: - try: - acquire() - try: - if wr in handlers: - handlers.remove(wr) - finally: - release() - except TypeError: - # https://bugs.python.org/issue21149 - If the RLock object behind - # acquire() and release() has been partially finalized you may see - # an error about NoneType not being callable. Absolutely nothing - # we can do in this GC during process shutdown situation. Eat it. - pass - -def _addHandlerRef(handler): - """ - Add a handler to the internal cleanup list using a weak reference. - """ - _acquireLock() - try: - _handlerList.append(weakref.ref(handler, _removeHandlerRef)) - finally: - _releaseLock() - -class Handler(Filterer): - """ - Handler instances dispatch logging events to specific destinations. - - The base handler class. Acts as a placeholder which defines the Handler - interface. Handlers can optionally use Formatter instances to format - records as desired. By default, no formatter is specified; in this case, - the 'raw' message as determined by record.message is logged. - """ - def __init__(self, level=NOTSET): - """ - Initializes the instance - basically setting the formatter to None - and the filter list to empty. - """ - Filterer.__init__(self) - self._name = None - self.level = _checkLevel(level) - self.formatter = None - # Add the handler to the global _handlerList (for cleanup on shutdown) - _addHandlerRef(self) - self.createLock() - - def get_name(self): - return self._name - - def set_name(self, name): - _acquireLock() - try: - if self._name in _handlers: - del _handlers[self._name] - self._name = name - if name: - _handlers[name] = self - finally: - _releaseLock() - - name = property(get_name, set_name) - - def createLock(self): - """ - Acquire a thread lock for serializing access to the underlying I/O. - """ - if thread: - self.lock = threading.RLock() - else: - self.lock = None - - def acquire(self): - """ - Acquire the I/O thread lock. - """ - if self.lock: - self.lock.acquire() - - def release(self): - """ - Release the I/O thread lock. - """ - if self.lock: - self.lock.release() - - def setLevel(self, level): - """ - Set the logging level of this handler. - """ - self.level = _checkLevel(level) - - def format(self, record): - """ - Format the specified record. - - If a formatter is set, use it. Otherwise, use the default formatter - for the module. - """ - if self.formatter: - fmt = self.formatter - else: - fmt = _defaultFormatter - return fmt.format(record) - - def emit(self, record): - """ - Do whatever it takes to actually log the specified logging record. - - This version is intended to be implemented by subclasses and so - raises a NotImplementedError. - """ - raise NotImplementedError('emit must be implemented ' - 'by Handler subclasses') - - def handle(self, record): - """ - Conditionally emit the specified logging record. - - Emission depends on filters which may have been added to the handler. - Wrap the actual emission of the record with acquisition/release of - the I/O thread lock. Returns whether the filter passed the record for - emission. - """ - rv = self.filter(record) - if rv: - self.acquire() - try: - self.emit(record) - finally: - self.release() - return rv - - def setFormatter(self, fmt): - """ - Set the formatter for this handler. - """ - self.formatter = fmt - - def flush(self): - """ - Ensure all logging output has been flushed. - - This version does nothing and is intended to be implemented by - subclasses. - """ - pass - - def close(self): - """ - Tidy up any resources used by the handler. - - This version removes the handler from an internal map of handlers, - _handlers, which is used for handler lookup by name. Subclasses - should ensure that this gets called from overridden close() - methods. - """ - #get the module data lock, as we're updating a shared structure. - _acquireLock() - try: #unlikely to raise an exception, but you never know... - if self._name and self._name in _handlers: - del _handlers[self._name] - finally: - _releaseLock() - - def handleError(self, record): - """ - Handle errors which occur during an emit() call. - - This method should be called from handlers when an exception is - encountered during an emit() call. If raiseExceptions is false, - exceptions get silently ignored. This is what is mostly wanted - for a logging system - most users will not care about errors in - the logging system, they are more interested in application errors. - You could, however, replace this with a custom handler if you wish. - The record which was being processed is passed in to this method. - """ - if raiseExceptions and sys.stderr: # see issue 13807 - ei = sys.exc_info() - try: - traceback.print_exception(ei[0], ei[1], ei[2], - None, sys.stderr) - sys.stderr.write('Logged from file %s, line %s\n' % ( - record.filename, record.lineno)) - except IOError: - pass # see issue 5971 - finally: - del ei - -class StreamHandler(Handler): - """ - A handler class which writes logging records, appropriately formatted, - to a stream. Note that this class does not close the stream, as - sys.stdout or sys.stderr may be used. - """ - - def __init__(self, stream=None): - """ - Initialize the handler. - - If stream is not specified, sys.stderr is used. - """ - Handler.__init__(self) - if stream is None: - stream = sys.stderr - self.stream = stream - - def flush(self): - """ - Flushes the stream. - """ - self.acquire() - try: - if self.stream and hasattr(self.stream, "flush"): - self.stream.flush() - finally: - self.release() - - def emit(self, record): - """ - Emit a record. - - If a formatter is specified, it is used to format the record. - The record is then written to the stream with a trailing newline. If - exception information is present, it is formatted using - traceback.print_exception and appended to the stream. If the stream - has an 'encoding' attribute, it is used to determine how to do the - output to the stream. - """ - try: - msg = self.format(record) - stream = self.stream - fs = "%s\r\n" - if not _unicode: #if no unicode support... - stream.write(fs % msg) - else: - try: - if (isinstance(msg, unicode) and - getattr(stream, 'encoding', None)): - ufs = u'%s\r\n' - try: - stream.write(ufs % msg) - except UnicodeEncodeError: - #Printing to terminals sometimes fails. For example, - #with an encoding of 'cp1251', the above write will - #work if written to a stream opened or wrapped by - #the codecs module, but fail when writing to a - #terminal even when the codepage is set to cp1251. - #An extra encoding step seems to be needed. - stream.write((ufs % msg).encode(stream.encoding)) - else: - stream.write(fs % msg) - except UnicodeError: - stream.write(fs % msg.encode("UTF-8")) - self.flush() - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - -class FileHandler(StreamHandler): - """ - A handler class which writes formatted logging records to disk files. - """ - def __init__(self, filename, mode='a', encoding=None, delay=0): - """ - Open the specified file and use it as the stream for logging. - """ - #keep the absolute path, otherwise derived classes which use this - #may come a cropper when the current directory changes - if codecs is None: - encoding = None - self.baseFilename = os.path.abspath(filename) - self.mode = mode - self.encoding = encoding - self.delay = delay - if delay: - #We don't open the stream, but we still need to call the - #Handler constructor to set level, formatter, lock etc. - Handler.__init__(self) - self.stream = None - else: - StreamHandler.__init__(self, self._open()) - - def close(self): - """ - Closes the stream. - """ - self.acquire() - try: - try: - if self.stream: - try: - self.flush() - finally: - stream = self.stream - self.stream = None - if hasattr(stream, "close"): - stream.close() - finally: - # Issue #19523: call unconditionally to - # prevent a handler leak when delay is set - StreamHandler.close(self) - finally: - self.release() - - def _open(self): - """ - Open the current base file with the (original) mode and encoding. - Return the resulting stream. - """ - if self.encoding is None: - stream = open(self.baseFilename, self.mode) - else: - stream = codecs.open(self.baseFilename, self.mode, self.encoding) - return stream - - def emit(self, record): - """ - Emit a record. - - If the stream was not opened because 'delay' was specified in the - constructor, open it before calling the superclass's emit. - """ - if self.stream is None: - self.stream = self._open() - StreamHandler.emit(self, record) - -#--------------------------------------------------------------------------- -# Manager classes and functions -#--------------------------------------------------------------------------- - -class PlaceHolder(object): - """ - PlaceHolder instances are used in the Manager logger hierarchy to take - the place of nodes for which no loggers have been defined. This class is - intended for internal use only and not as part of the public API. - """ - def __init__(self, alogger): - """ - Initialize with the specified logger being a child of this placeholder. - """ - #self.loggers = [alogger] - self.loggerMap = { alogger : None } - - def append(self, alogger): - """ - Add the specified logger as a child of this placeholder. - """ - #if alogger not in self.loggers: - if alogger not in self.loggerMap: - #self.loggers.append(alogger) - self.loggerMap[alogger] = None - -# -# Determine which class to use when instantiating loggers. -# -_loggerClass = None - -def setLoggerClass(klass): - """ - Set the class to be used when instantiating a logger. The class should - define __init__() such that only a name argument is required, and the - __init__() should call Logger.__init__() - """ - if klass != Logger: - if not issubclass(klass, Logger): - raise TypeError("logger not derived from logging.Logger: " - + klass.__name__) - global _loggerClass - _loggerClass = klass - -def getLoggerClass(): - """ - Return the class to be used when instantiating a logger. - """ - - return _loggerClass - -class Manager(object): - """ - There is [under normal circumstances] just one Manager instance, which - holds the hierarchy of loggers. - """ - def __init__(self, rootnode): - """ - Initialize the manager with the root node of the logger hierarchy. - """ - self.root = rootnode - self.disable = 0 - self.emittedNoHandlerWarning = 0 - self.loggerDict = {} - self.loggerClass = None - - def getLogger(self, name): - """ - Get a logger with the specified name (channel name), creating it - if it doesn't yet exist. This name is a dot-separated hierarchical - name, such as "a", "a.b", "a.b.c" or similar. - - If a PlaceHolder existed for the specified name [i.e. the logger - didn't exist but a child of it did], replace it with the created - logger and fix up the parent/child references which pointed to the - placeholder to now point to the logger. - """ - rv = None - if not isinstance(name, basestring): - raise TypeError('A logger name must be string or Unicode') - if isinstance(name, unicode): - name = name.encode('utf-8') - _acquireLock() - try: - if name in self.loggerDict: - rv = self.loggerDict[name] - if isinstance(rv, PlaceHolder): - ph = rv - rv = (self.loggerClass or _loggerClass)(name) - rv.manager = self - self.loggerDict[name] = rv - self._fixupChildren(ph, rv) - self._fixupParents(rv) - else: - rv = (self.loggerClass or _loggerClass)(name) - rv.manager = self - self.loggerDict[name] = rv - self._fixupParents(rv) - finally: - _releaseLock() - return rv - - def setLoggerClass(self, klass): - """ - Set the class to be used when instantiating a logger with this Manager. - """ - if klass != Logger: - if not issubclass(klass, Logger): - raise TypeError("logger not derived from logging.Logger: " - + klass.__name__) - self.loggerClass = klass - - def _fixupParents(self, alogger): - """ - Ensure that there are either loggers or placeholders all the way - from the specified logger to the root of the logger hierarchy. - """ - name = alogger.name - i = name.rfind(".") - rv = None - while (i > 0) and not rv: - substr = name[:i] - if substr not in self.loggerDict: - self.loggerDict[substr] = PlaceHolder(alogger) - else: - obj = self.loggerDict[substr] - if isinstance(obj, Logger): - rv = obj - else: - assert isinstance(obj, PlaceHolder) - obj.append(alogger) - i = name.rfind(".", 0, i - 1) - if not rv: - rv = self.root - alogger.parent = rv - - def _fixupChildren(self, ph, alogger): - """ - Ensure that children of the placeholder ph are connected to the - specified logger. - """ - name = alogger.name - namelen = len(name) - for c in ph.loggerMap.keys(): - #The if means ... if not c.parent.name.startswith(nm) - if c.parent.name[:namelen] != name: - alogger.parent = c.parent - c.parent = alogger - -#--------------------------------------------------------------------------- -# Logger classes and functions -#--------------------------------------------------------------------------- - -class Logger(Filterer): - """ - Instances of the Logger class represent a single logging channel. A - "logging channel" indicates an area of an application. Exactly how an - "area" is defined is up to the application developer. Since an - application can have any number of areas, logging channels are identified - by a unique string. Application areas can be nested (e.g. an area - of "input processing" might include sub-areas "read CSV files", "read - XLS files" and "read Gnumeric files"). To cater for this natural nesting, - channel names are organized into a namespace hierarchy where levels are - separated by periods, much like the Java or Python package namespace. So - in the instance given above, channel names might be "input" for the upper - level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels. - There is no arbitrary limit to the depth of nesting. - """ - def __init__(self, name, level=NOTSET): - """ - Initialize the logger with a name and an optional level. - """ - Filterer.__init__(self) - self.name = name - self.level = _checkLevel(level) - self.parent = None - self.propagate = 1 - self.handlers = [] - self.disabled = 0 - - def setLevel(self, level): - """ - Set the logging level of this logger. - """ - self.level = _checkLevel(level) - - def debug(self, msg, *args, **kwargs): - """ - Log 'msg % args' with severity 'DEBUG'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) - """ - if self.isEnabledFor(DEBUG): - self._log(DEBUG, msg, args, **kwargs) - - def info(self, msg, *args, **kwargs): - """ - Log 'msg % args' with severity 'INFO'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.info("Houston, we have a %s", "interesting problem", exc_info=1) - """ - if self.isEnabledFor(INFO): - self._log(INFO, msg, args, **kwargs) - - def warning(self, msg, *args, **kwargs): - """ - Log 'msg % args' with severity 'WARNING'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1) - """ - if self.isEnabledFor(WARNING): - self._log(WARNING, msg, args, **kwargs) - - warn = warning - - def error(self, msg, *args, **kwargs): - """ - Log 'msg % args' with severity 'ERROR'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.error("Houston, we have a %s", "major problem", exc_info=1) - """ - if self.isEnabledFor(ERROR): - self._log(ERROR, msg, args, **kwargs) - - def exception(self, msg, *args, **kwargs): - """ - Convenience method for logging an ERROR with exception information. - """ - kwargs['exc_info'] = 1 - self.error(msg, *args, **kwargs) - - def critical(self, msg, *args, **kwargs): - """ - Log 'msg % args' with severity 'CRITICAL'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.critical("Houston, we have a %s", "major disaster", exc_info=1) - """ - if self.isEnabledFor(CRITICAL): - self._log(CRITICAL, msg, args, **kwargs) - - fatal = critical - - def log(self, level, msg, *args, **kwargs): - """ - Log 'msg % args' with the integer severity 'level'. - - To pass exception information, use the keyword argument exc_info with - a true value, e.g. - - logger.log(level, "We have a %s", "mysterious problem", exc_info=1) - """ - if not isinstance(level, (int, long)): - if raiseExceptions: - raise TypeError("level must be an integer") - else: - return - if self.isEnabledFor(level): - self._log(level, msg, args, **kwargs) - - def findCaller(self): - """ - Find the stack frame of the caller so that we can note the source - file name, line number and function name. - """ - f = currentframe() - #On some versions of IronPython, currentframe() returns None if - #IronPython isn't run with -X:Frames. - if f is not None: - f = f.f_back - rv = "(unknown file)", 0, "(unknown function)" - while hasattr(f, "f_code"): - co = f.f_code - filename = os.path.normcase(co.co_filename) - if filename == _srcfile: - f = f.f_back - continue - rv = (co.co_filename, f.f_lineno, co.co_name) - break - return rv - - def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): - """ - A factory method which can be overridden in subclasses to create - specialized LogRecords. - """ - rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func) - if extra is not None: - for key in extra: - if (key in ["message", "asctime"]) or (key in rv.__dict__): - raise KeyError("Attempt to overwrite %r in LogRecord" % key) - rv.__dict__[key] = extra[key] - return rv - - def _log(self, level, msg, args, exc_info=None, extra=None): - """ - Low-level logging routine which creates a LogRecord and then calls - all the handlers of this logger to handle the record. - """ - if _srcfile: - #IronPython doesn't track Python frames, so findCaller raises an - #exception on some versions of IronPython. We trap it here so that - #IronPython can use logging. - try: - fn, lno, func = self.findCaller() - except ValueError: - fn, lno, func = "(unknown file)", 0, "(unknown function)" - else: - fn, lno, func = "(unknown file)", 0, "(unknown function)" - if exc_info: - if not isinstance(exc_info, tuple): - exc_info = sys.exc_info() - record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra) - self.handle(record) - - def handle(self, record): - """ - Call the handlers for the specified record. - - This method is used for unpickled records received from a socket, as - well as those created locally. Logger-level filtering is applied. - """ - if (not self.disabled) and self.filter(record): - self.callHandlers(record) - - def addHandler(self, hdlr): - """ - Add the specified handler to this logger. - """ - _acquireLock() - try: - if not (hdlr in self.handlers): - self.handlers.append(hdlr) - finally: - _releaseLock() - - def removeHandler(self, hdlr): - """ - Remove the specified handler from this logger. - """ - _acquireLock() - try: - if hdlr in self.handlers: - self.handlers.remove(hdlr) - finally: - _releaseLock() - - def callHandlers(self, record): - """ - Pass a record to all relevant handlers. - - Loop through all handlers for this logger and its parents in the - logger hierarchy. If no handler was found, output a one-off error - message to sys.stderr. Stop searching up the hierarchy whenever a - logger with the "propagate" attribute set to zero is found - that - will be the last logger whose handlers are called. - """ - c = self - found = 0 - while c: - for hdlr in c.handlers: - found = found + 1 - if record.levelno >= hdlr.level: - hdlr.handle(record) - if not c.propagate: - c = None #break out - else: - c = c.parent - if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning: - sys.stderr.write("No handlers could be found for logger" - " \"%s\"\n" % self.name) - self.manager.emittedNoHandlerWarning = 1 - - def getEffectiveLevel(self): - """ - Get the effective level for this logger. - - Loop through this logger and its parents in the logger hierarchy, - looking for a non-zero logging level. Return the first one found. - """ - logger = self - while logger: - if logger.level: - return logger.level - logger = logger.parent - return NOTSET - - def isEnabledFor(self, level): - """ - Is this logger enabled for level 'level'? - """ - if self.manager.disable >= level: - return 0 - return level >= self.getEffectiveLevel() - - def getChild(self, suffix): - """ - Get a logger which is a descendant to this one. - - This is a convenience method, such that - - logging.getLogger('abc').getChild('def.ghi') - - is the same as - - logging.getLogger('abc.def.ghi') - - It's useful, for example, when the parent logger is named using - __name__ rather than a literal string. - """ - if self.root is not self: - suffix = '.'.join((self.name, suffix)) - return self.manager.getLogger(suffix) - -class RootLogger(Logger): - """ - A root logger is not that different to any other logger, except that - it must have a logging level and there is only one instance of it in - the hierarchy. - """ - def __init__(self, level): - """ - Initialize the logger with the name "root". - """ - Logger.__init__(self, "root", level) - -_loggerClass = Logger - -class LoggerAdapter(object): - """ - An adapter for loggers which makes it easier to specify contextual - information in logging output. - """ - - def __init__(self, logger, extra): - """ - Initialize the adapter with a logger and a dict-like object which - provides contextual information. This constructor signature allows - easy stacking of LoggerAdapters, if so desired. - - You can effectively pass keyword arguments as shown in the - following example: - - adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2")) - """ - self.logger = logger - self.extra = extra - - def process(self, msg, kwargs): - """ - Process the logging message and keyword arguments passed in to - a logging call to insert contextual information. You can either - manipulate the message itself, the keyword args or both. Return - the message and kwargs modified (or not) to suit your needs. - - Normally, you'll only need to override this one method in a - LoggerAdapter subclass for your specific needs. - """ - kwargs["extra"] = self.extra - return msg, kwargs - - def debug(self, msg, *args, **kwargs): - """ - Delegate a debug call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.debug(msg, *args, **kwargs) - - def info(self, msg, *args, **kwargs): - """ - Delegate an info call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.info(msg, *args, **kwargs) - - def warning(self, msg, *args, **kwargs): - """ - Delegate a warning call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.warning(msg, *args, **kwargs) - - def error(self, msg, *args, **kwargs): - """ - Delegate an error call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.error(msg, *args, **kwargs) - - def exception(self, msg, *args, **kwargs): - """ - Delegate an exception call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - kwargs["exc_info"] = 1 - self.logger.error(msg, *args, **kwargs) - - def critical(self, msg, *args, **kwargs): - """ - Delegate a critical call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.critical(msg, *args, **kwargs) - - def log(self, level, msg, *args, **kwargs): - """ - Delegate a log call to the underlying logger, after adding - contextual information from this adapter instance. - """ - msg, kwargs = self.process(msg, kwargs) - self.logger.log(level, msg, *args, **kwargs) - - def isEnabledFor(self, level): - """ - See if the underlying logger is enabled for the specified level. - """ - return self.logger.isEnabledFor(level) - -root = RootLogger(WARNING) -Logger.root = root -Logger.manager = Manager(Logger.root) - -#--------------------------------------------------------------------------- -# Configuration classes and functions -#--------------------------------------------------------------------------- - -BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" - -def basicConfig(**kwargs): - """ - Do basic configuration for the logging system. - - This function does nothing if the root logger already has handlers - configured. It is a convenience method intended for use by simple scripts - to do one-shot configuration of the logging package. - - The default behaviour is to create a StreamHandler which writes to - sys.stderr, set a formatter using the BASIC_FORMAT format string, and - add the handler to the root logger. - - A number of optional keyword arguments may be specified, which can alter - the default behaviour. - - filename Specifies that a FileHandler be created, using the specified - filename, rather than a StreamHandler. - filemode Specifies the mode to open the file, if filename is specified - (if filemode is unspecified, it defaults to 'a'). - format Use the specified format string for the handler. - datefmt Use the specified date/time format. - level Set the root logger level to the specified level. - stream Use the specified stream to initialize the StreamHandler. Note - that this argument is incompatible with 'filename' - if both - are present, 'stream' is ignored. - - Note that you could specify a stream created using open(filename, mode) - rather than passing the filename and mode in. However, it should be - remembered that StreamHandler does not close its stream (since it may be - using sys.stdout or sys.stderr), whereas FileHandler closes its stream - when the handler is closed. - """ - # Add thread safety in case someone mistakenly calls - # basicConfig() from multiple threads - _acquireLock() - try: - if len(root.handlers) == 0: - filename = kwargs.get("filename") - if filename: - mode = kwargs.get("filemode", 'a') - hdlr = FileHandler(filename, mode) - else: - stream = kwargs.get("stream") - hdlr = StreamHandler(stream) - fs = kwargs.get("format", BASIC_FORMAT) - dfs = kwargs.get("datefmt", None) - fmt = Formatter(fs, dfs) - hdlr.setFormatter(fmt) - root.addHandler(hdlr) - level = kwargs.get("level") - if level is not None: - root.setLevel(level) - finally: - _releaseLock() - -#--------------------------------------------------------------------------- -# Utility functions at module level. -# Basically delegate everything to the root logger. -#--------------------------------------------------------------------------- - -def getLogger(name=None): - """ - Return a logger with the specified name, creating it if necessary. - - If no name is specified, return the root logger. - """ - if name: - return Logger.manager.getLogger(name) - else: - return root - -#def getRootLogger(): -# """ -# Return the root logger. -# -# Note that getLogger('') now does the same thing, so this function is -# deprecated and may disappear in the future. -# """ -# return root - -def critical(msg, *args, **kwargs): - """ - Log a message with severity 'CRITICAL' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.critical(msg, *args, **kwargs) - -fatal = critical - -def error(msg, *args, **kwargs): - """ - Log a message with severity 'ERROR' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.error(msg, *args, **kwargs) - -def exception(msg, *args, **kwargs): - """ - Log a message with severity 'ERROR' on the root logger, - with exception information. - """ - kwargs['exc_info'] = 1 - error(msg, *args, **kwargs) - -def warning(msg, *args, **kwargs): - """ - Log a message with severity 'WARNING' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.warning(msg, *args, **kwargs) - -warn = warning - -def info(msg, *args, **kwargs): - """ - Log a message with severity 'INFO' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.info(msg, *args, **kwargs) - -def debug(msg, *args, **kwargs): - """ - Log a message with severity 'DEBUG' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.debug(msg, *args, **kwargs) - -def log(level, msg, *args, **kwargs): - """ - Log 'msg % args' with the integer severity 'level' on the root logger. - """ - if len(root.handlers) == 0: - basicConfig() - root.log(level, msg, *args, **kwargs) - -def disable(level): - """ - Disable all logging calls of severity 'level' and below. - """ - root.manager.disable = level - -def shutdown(handlerList=_handlerList): - """ - Perform any cleanup actions in the logging system (e.g. flushing - buffers). - - Should be called at application exit. - """ - for wr in reversed(handlerList[:]): - #errors might occur, for example, if files are locked - #we just ignore them if raiseExceptions is not set - try: - h = wr() - if h: - try: - h.acquire() - h.flush() - h.close() - except (IOError, ValueError): - # Ignore errors which might be caused - # because handlers have been closed but - # references to them are still around at - # application exit. - pass - finally: - h.release() - except: - if raiseExceptions: - raise - #else, swallow - -#Let's try and shutdown automatically on application exit... -import atexit -atexit.register(shutdown) - -# Null handler - -class NullHandler(Handler): - """ - This handler does nothing. It's intended to be used to avoid the - "No handlers could be found for logger XXX" one-off warning. This is - important for library code, which may contain code to log events. If a user - of the library does not configure logging, the one-off warning might be - produced; to avoid this, the library developer simply needs to instantiate - a NullHandler and add it to the top-level logger of the library module or - package. - """ - def handle(self, record): - pass - - def emit(self, record): - pass - - def createLock(self): - self.lock = None - -# Warnings integration - -_warnings_showwarning = None - -def _showwarning(message, category, filename, lineno, file=None, line=None): - """ - Implementation of showwarnings which redirects to logging, which will first - check to see if the file parameter is None. If a file is specified, it will - delegate to the original warnings implementation of showwarning. Otherwise, - it will call warnings.formatwarning and will log the resulting string to a - warnings logger named "py.warnings" with level logging.WARNING. - """ - if file is not None: - if _warnings_showwarning is not None: - _warnings_showwarning(message, category, filename, lineno, file, line) - else: - s = warnings.formatwarning(message, category, filename, lineno, line) - logger = getLogger("py.warnings") - if not logger.handlers: - logger.addHandler(NullHandler()) - logger.warning("%s", s) - -def captureWarnings(capture): - """ - If capture is true, redirect all warnings to the logging package. - If capture is False, ensure that warnings are not redirected to logging - but to their original destinations. - """ - global _warnings_showwarning - if capture: - if _warnings_showwarning is None: - _warnings_showwarning = warnings.showwarning - warnings.showwarning = _showwarning - else: - if _warnings_showwarning is not None: - warnings.showwarning = _warnings_showwarning - _warnings_showwarning = None diff --git a/Monika After Story/game/python-packages/logging/config.py b/Monika After Story/game/python-packages/logging/config.py deleted file mode 100644 index 8b3795675d..0000000000 --- a/Monika After Story/game/python-packages/logging/config.py +++ /dev/null @@ -1,919 +0,0 @@ -# Copyright 2001-2014 by Vinay Sajip. All Rights Reserved. -# -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appear in all copies and that -# both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of Vinay Sajip -# not be used in advertising or publicity pertaining to distribution -# of the software without specific, written prior permission. -# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING -# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL -# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR -# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER -# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -""" -Configuration functions for the logging package for Python. The core package -is based on PEP 282 and comments thereto in comp.lang.python, and influenced -by Apache's log4j system. - -Copyright (C) 2001-2014 Vinay Sajip. All Rights Reserved. - -To use, simply 'import logging' and log away! -""" - -import cStringIO -import errno -import io -import logging -import logging.handlers -import os -import re -import socket -import struct -import sys -import traceback -import types - -try: - import thread - import threading -except ImportError: - thread = None - -from SocketServer import ThreadingTCPServer, StreamRequestHandler - - -DEFAULT_LOGGING_CONFIG_PORT = 9030 - -RESET_ERROR = errno.ECONNRESET - -# -# The following code implements a socket listener for on-the-fly -# reconfiguration of logging. -# -# _listener holds the server object doing the listening -_listener = None - -def fileConfig(fname, defaults=None, disable_existing_loggers=True): - """ - Read the logging configuration from a ConfigParser-format file. - - This can be called several times from an application, allowing an end user - the ability to select from various pre-canned configurations (if the - developer provides a mechanism to present the choices and load the chosen - configuration). - """ - import ConfigParser - - cp = ConfigParser.ConfigParser(defaults) - if hasattr(fname, 'readline'): - cp.readfp(fname) - else: - cp.read(fname) - - formatters = _create_formatters(cp) - - # critical section - logging._acquireLock() - try: - logging._handlers.clear() - del logging._handlerList[:] - # Handlers add themselves to logging._handlers - handlers = _install_handlers(cp, formatters) - _install_loggers(cp, handlers, disable_existing_loggers) - finally: - logging._releaseLock() - - -def _resolve(name): - """Resolve a dotted name to a global object.""" - name = name.split('.') - used = name.pop(0) - found = __import__(used) - for n in name: - used = used + '.' + n - try: - found = getattr(found, n) - except AttributeError: - __import__(used) - found = getattr(found, n) - return found - -def _strip_spaces(alist): - return map(lambda x: x.strip(), alist) - -def _encoded(s): - return s if isinstance(s, str) else s.encode('utf-8') - -def _create_formatters(cp): - """Create and return formatters""" - flist = cp.get("formatters", "keys") - if not len(flist): - return {} - flist = flist.split(",") - flist = _strip_spaces(flist) - formatters = {} - for form in flist: - sectname = "formatter_%s" % form - opts = cp.options(sectname) - if "format" in opts: - fs = cp.get(sectname, "format", 1) - else: - fs = None - if "datefmt" in opts: - dfs = cp.get(sectname, "datefmt", 1) - else: - dfs = None - c = logging.Formatter - if "class" in opts: - class_name = cp.get(sectname, "class") - if class_name: - c = _resolve(class_name) - f = c(fs, dfs) - formatters[form] = f - return formatters - - -def _install_handlers(cp, formatters): - """Install and return handlers""" - hlist = cp.get("handlers", "keys") - if not len(hlist): - return {} - hlist = hlist.split(",") - hlist = _strip_spaces(hlist) - handlers = {} - fixups = [] #for inter-handler references - for hand in hlist: - sectname = "handler_%s" % hand - klass = cp.get(sectname, "class") - opts = cp.options(sectname) - if "formatter" in opts: - fmt = cp.get(sectname, "formatter") - else: - fmt = "" - try: - klass = eval(klass, vars(logging)) - except (AttributeError, NameError): - klass = _resolve(klass) - args = cp.get(sectname, "args") - args = eval(args, vars(logging)) - h = klass(*args) - if "level" in opts: - level = cp.get(sectname, "level") - h.setLevel(logging._levelNames[level]) - if len(fmt): - h.setFormatter(formatters[fmt]) - if issubclass(klass, logging.handlers.MemoryHandler): - if "target" in opts: - target = cp.get(sectname,"target") - else: - target = "" - if len(target): #the target handler may not be loaded yet, so keep for later... - fixups.append((h, target)) - handlers[hand] = h - #now all handlers are loaded, fixup inter-handler references... - for h, t in fixups: - h.setTarget(handlers[t]) - return handlers - - -def _install_loggers(cp, handlers, disable_existing_loggers): - """Create and install loggers""" - - # configure the root first - llist = cp.get("loggers", "keys") - llist = llist.split(",") - llist = list(map(lambda x: x.strip(), llist)) - llist.remove("root") - sectname = "logger_root" - root = logging.root - log = root - opts = cp.options(sectname) - if "level" in opts: - level = cp.get(sectname, "level") - log.setLevel(logging._levelNames[level]) - for h in root.handlers[:]: - root.removeHandler(h) - hlist = cp.get(sectname, "handlers") - if len(hlist): - hlist = hlist.split(",") - hlist = _strip_spaces(hlist) - for hand in hlist: - log.addHandler(handlers[hand]) - - #and now the others... - #we don't want to lose the existing loggers, - #since other threads may have pointers to them. - #existing is set to contain all existing loggers, - #and as we go through the new configuration we - #remove any which are configured. At the end, - #what's left in existing is the set of loggers - #which were in the previous configuration but - #which are not in the new configuration. - existing = list(root.manager.loggerDict.keys()) - #The list needs to be sorted so that we can - #avoid disabling child loggers of explicitly - #named loggers. With a sorted list it is easier - #to find the child loggers. - existing.sort() - #We'll keep the list of existing loggers - #which are children of named loggers here... - child_loggers = [] - #now set up the new ones... - for log in llist: - sectname = "logger_%s" % log - qn = cp.get(sectname, "qualname") - opts = cp.options(sectname) - if "propagate" in opts: - propagate = cp.getint(sectname, "propagate") - else: - propagate = 1 - logger = logging.getLogger(qn) - if qn in existing: - i = existing.index(qn) + 1 # start with the entry after qn - prefixed = qn + "." - pflen = len(prefixed) - num_existing = len(existing) - while i < num_existing: - if existing[i][:pflen] == prefixed: - child_loggers.append(existing[i]) - i += 1 - existing.remove(qn) - if "level" in opts: - level = cp.get(sectname, "level") - logger.setLevel(logging._levelNames[level]) - for h in logger.handlers[:]: - logger.removeHandler(h) - logger.propagate = propagate - logger.disabled = 0 - hlist = cp.get(sectname, "handlers") - if len(hlist): - hlist = hlist.split(",") - hlist = _strip_spaces(hlist) - for hand in hlist: - logger.addHandler(handlers[hand]) - - #Disable any old loggers. There's no point deleting - #them as other threads may continue to hold references - #and by disabling them, you stop them doing any logging. - #However, don't disable children of named loggers, as that's - #probably not what was intended by the user. - for log in existing: - logger = root.manager.loggerDict[log] - if log in child_loggers: - logger.level = logging.NOTSET - logger.handlers = [] - logger.propagate = 1 - else: - logger.disabled = disable_existing_loggers - - - -IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) - - -def valid_ident(s): - m = IDENTIFIER.match(s) - if not m: - raise ValueError('Not a valid Python identifier: %r' % s) - return True - - -class ConvertingMixin(object): - """For ConvertingXXX's, this mixin class provides common functions""" - - def convert_with_key(self, key, value, replace=True): - result = self.configurator.convert(value) - #If the converted value is different, save for next time - if value is not result: - if replace: - self[key] = result - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - result.key = key - return result - - def convert(self, value): - result = self.configurator.convert(value) - if value is not result: - if type(result) in (ConvertingDict, ConvertingList, - ConvertingTuple): - result.parent = self - return result - - -# The ConvertingXXX classes are wrappers around standard Python containers, -# and they serve to convert any suitable values in the container. The -# conversion converts base dicts, lists and tuples to their wrapped -# equivalents, whereas strings which match a conversion format are converted -# appropriately. -# -# Each wrapper should have a configurator attribute holding the actual -# configurator to use for conversion. - -class ConvertingDict(dict, ConvertingMixin): - """A converting dictionary wrapper.""" - - def __getitem__(self, key): - value = dict.__getitem__(self, key) - return self.convert_with_key(key, value) - - def get(self, key, default=None): - value = dict.get(self, key, default) - return self.convert_with_key(key, value) - - def pop(self, key, default=None): - value = dict.pop(self, key, default) - return self.convert_with_key(key, value, replace=False) - -class ConvertingList(list, ConvertingMixin): - """A converting list wrapper.""" - def __getitem__(self, key): - value = list.__getitem__(self, key) - return self.convert_with_key(key, value) - - def pop(self, idx=-1): - value = list.pop(self, idx) - return self.convert(value) - -class ConvertingTuple(tuple, ConvertingMixin): - """A converting tuple wrapper.""" - def __getitem__(self, key): - value = tuple.__getitem__(self, key) - # Can't replace a tuple entry. - return self.convert_with_key(key, value, replace=False) - -class BaseConfigurator(object): - """ - The configurator base class which defines some useful defaults. - """ - - CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') - - WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') - DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') - INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') - DIGIT_PATTERN = re.compile(r'^\d+$') - - value_converters = { - 'ext' : 'ext_convert', - 'cfg' : 'cfg_convert', - } - - # We might want to use a different one, e.g. importlib - importer = __import__ - - def __init__(self, config): - self.config = ConvertingDict(config) - self.config.configurator = self - # Issue 12718: winpdb replaces __import__ with a Python function, which - # ends up being treated as a bound method. To avoid problems, we - # set the importer on the instance, but leave it defined in the class - # so existing code doesn't break - if type(__import__) == types.FunctionType: - self.importer = __import__ - - def resolve(self, s): - """ - Resolve strings to objects using standard import and attribute - syntax. - """ - name = s.split('.') - used = name.pop(0) - try: - found = self.importer(used) - for frag in name: - used += '.' + frag - try: - found = getattr(found, frag) - except AttributeError: - self.importer(used) - found = getattr(found, frag) - return found - except ImportError: - e, tb = sys.exc_info()[1:] - v = ValueError('Cannot resolve %r: %s' % (s, e)) - v.__cause__, v.__traceback__ = e, tb - raise v - - def ext_convert(self, value): - """Default converter for the ext:// protocol.""" - return self.resolve(value) - - def cfg_convert(self, value): - """Default converter for the cfg:// protocol.""" - rest = value - m = self.WORD_PATTERN.match(rest) - if m is None: - raise ValueError("Unable to convert %r" % value) - else: - rest = rest[m.end():] - d = self.config[m.groups()[0]] - #print d, rest - while rest: - m = self.DOT_PATTERN.match(rest) - if m: - d = d[m.groups()[0]] - else: - m = self.INDEX_PATTERN.match(rest) - if m: - idx = m.groups()[0] - if not self.DIGIT_PATTERN.match(idx): - d = d[idx] - else: - try: - n = int(idx) # try as number first (most likely) - d = d[n] - except TypeError: - d = d[idx] - if m: - rest = rest[m.end():] - else: - raise ValueError('Unable to convert ' - '%r at %r' % (value, rest)) - #rest should be empty - return d - - def convert(self, value): - """ - Convert values to an appropriate type. dicts, lists and tuples are - replaced by their converting alternatives. Strings are checked to - see if they have a conversion format and are converted if they do. - """ - if not isinstance(value, ConvertingDict) and isinstance(value, dict): - value = ConvertingDict(value) - value.configurator = self - elif not isinstance(value, ConvertingList) and isinstance(value, list): - value = ConvertingList(value) - value.configurator = self - elif not isinstance(value, ConvertingTuple) and\ - isinstance(value, tuple): - value = ConvertingTuple(value) - value.configurator = self - elif isinstance(value, basestring): # str for py3k - m = self.CONVERT_PATTERN.match(value) - if m: - d = m.groupdict() - prefix = d['prefix'] - converter = self.value_converters.get(prefix, None) - if converter: - suffix = d['suffix'] - converter = getattr(self, converter) - value = converter(suffix) - return value - - def configure_custom(self, config): - """Configure an object with a user-supplied factory.""" - c = config.pop('()') - if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: - c = self.resolve(c) - props = config.pop('.', None) - # Check for valid identifiers - kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) - result = c(**kwargs) - if props: - for name, value in props.items(): - setattr(result, name, value) - return result - - def as_tuple(self, value): - """Utility function which converts lists to tuples.""" - if isinstance(value, list): - value = tuple(value) - return value - -class DictConfigurator(BaseConfigurator): - """ - Configure logging using a dictionary-like object to describe the - configuration. - """ - - def configure(self): - """Do the configuration.""" - - config = self.config - if 'version' not in config: - raise ValueError("dictionary doesn't specify a version") - if config['version'] != 1: - raise ValueError("Unsupported version: %s" % config['version']) - incremental = config.pop('incremental', False) - EMPTY_DICT = {} - logging._acquireLock() - try: - if incremental: - handlers = config.get('handlers', EMPTY_DICT) - for name in handlers: - if name not in logging._handlers: - raise ValueError('No handler found with ' - 'name %r' % name) - else: - try: - handler = logging._handlers[name] - handler_config = handlers[name] - level = handler_config.get('level', None) - if level: - handler.setLevel(logging._checkLevel(level)) - except StandardError as e: - raise ValueError('Unable to configure handler ' - '%r: %s' % (name, e)) - loggers = config.get('loggers', EMPTY_DICT) - for name in loggers: - try: - self.configure_logger(name, loggers[name], True) - except StandardError as e: - raise ValueError('Unable to configure logger ' - '%r: %s' % (name, e)) - root = config.get('root', None) - if root: - try: - self.configure_root(root, True) - except StandardError as e: - raise ValueError('Unable to configure root ' - 'logger: %s' % e) - else: - disable_existing = config.pop('disable_existing_loggers', True) - - logging._handlers.clear() - del logging._handlerList[:] - - # Do formatters first - they don't refer to anything else - formatters = config.get('formatters', EMPTY_DICT) - for name in formatters: - try: - formatters[name] = self.configure_formatter( - formatters[name]) - except StandardError as e: - raise ValueError('Unable to configure ' - 'formatter %r: %s' % (name, e)) - # Next, do filters - they don't refer to anything else, either - filters = config.get('filters', EMPTY_DICT) - for name in filters: - try: - filters[name] = self.configure_filter(filters[name]) - except StandardError as e: - raise ValueError('Unable to configure ' - 'filter %r: %s' % (name, e)) - - # Next, do handlers - they refer to formatters and filters - # As handlers can refer to other handlers, sort the keys - # to allow a deterministic order of configuration - handlers = config.get('handlers', EMPTY_DICT) - deferred = [] - for name in sorted(handlers): - try: - handler = self.configure_handler(handlers[name]) - handler.name = name - handlers[name] = handler - except StandardError as e: - if 'target not configured yet' in str(e): - deferred.append(name) - else: - raise ValueError('Unable to configure handler ' - '%r: %s' % (name, e)) - - # Now do any that were deferred - for name in deferred: - try: - handler = self.configure_handler(handlers[name]) - handler.name = name - handlers[name] = handler - except StandardError as e: - raise ValueError('Unable to configure handler ' - '%r: %s' % (name, e)) - - # Next, do loggers - they refer to handlers and filters - - #we don't want to lose the existing loggers, - #since other threads may have pointers to them. - #existing is set to contain all existing loggers, - #and as we go through the new configuration we - #remove any which are configured. At the end, - #what's left in existing is the set of loggers - #which were in the previous configuration but - #which are not in the new configuration. - root = logging.root - existing = root.manager.loggerDict.keys() - #The list needs to be sorted so that we can - #avoid disabling child loggers of explicitly - #named loggers. With a sorted list it is easier - #to find the child loggers. - existing.sort() - #We'll keep the list of existing loggers - #which are children of named loggers here... - child_loggers = [] - #now set up the new ones... - loggers = config.get('loggers', EMPTY_DICT) - for name in loggers: - name = _encoded(name) - if name in existing: - i = existing.index(name) - prefixed = name + "." - pflen = len(prefixed) - num_existing = len(existing) - i = i + 1 # look at the entry after name - while (i < num_existing) and\ - (existing[i][:pflen] == prefixed): - child_loggers.append(existing[i]) - i = i + 1 - existing.remove(name) - try: - self.configure_logger(name, loggers[name]) - except StandardError as e: - raise ValueError('Unable to configure logger ' - '%r: %s' % (name, e)) - - #Disable any old loggers. There's no point deleting - #them as other threads may continue to hold references - #and by disabling them, you stop them doing any logging. - #However, don't disable children of named loggers, as that's - #probably not what was intended by the user. - for log in existing: - logger = root.manager.loggerDict[log] - if log in child_loggers: - logger.level = logging.NOTSET - logger.handlers = [] - logger.propagate = True - elif disable_existing: - logger.disabled = True - - # And finally, do the root logger - root = config.get('root', None) - if root: - try: - self.configure_root(root) - except StandardError as e: - raise ValueError('Unable to configure root ' - 'logger: %s' % e) - finally: - logging._releaseLock() - - def configure_formatter(self, config): - """Configure a formatter from a dictionary.""" - if '()' in config: - factory = config['()'] # for use in exception handler - try: - result = self.configure_custom(config) - except TypeError as te: - if "'format'" not in str(te): - raise - #Name of parameter changed from fmt to format. - #Retry with old name. - #This is so that code can be used with older Python versions - #(e.g. by Django) - config['fmt'] = config.pop('format') - config['()'] = factory - result = self.configure_custom(config) - else: - fmt = config.get('format', None) - dfmt = config.get('datefmt', None) - result = logging.Formatter(fmt, dfmt) - return result - - def configure_filter(self, config): - """Configure a filter from a dictionary.""" - if '()' in config: - result = self.configure_custom(config) - else: - name = config.get('name', '') - result = logging.Filter(name) - return result - - def add_filters(self, filterer, filters): - """Add filters to a filterer from a list of names.""" - for f in filters: - try: - filterer.addFilter(self.config['filters'][f]) - except StandardError as e: - raise ValueError('Unable to add filter %r: %s' % (f, e)) - - def configure_handler(self, config): - """Configure a handler from a dictionary.""" - formatter = config.pop('formatter', None) - if formatter: - try: - formatter = self.config['formatters'][formatter] - except StandardError as e: - raise ValueError('Unable to set formatter ' - '%r: %s' % (formatter, e)) - level = config.pop('level', None) - filters = config.pop('filters', None) - if '()' in config: - c = config.pop('()') - if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: - c = self.resolve(c) - factory = c - else: - cname = config.pop('class') - klass = self.resolve(cname) - #Special case for handler which refers to another handler - if issubclass(klass, logging.handlers.MemoryHandler) and\ - 'target' in config: - try: - th = self.config['handlers'][config['target']] - if not isinstance(th, logging.Handler): - config['class'] = cname # restore for deferred configuration - raise StandardError('target not configured yet') - config['target'] = th - except StandardError as e: - raise ValueError('Unable to set target handler ' - '%r: %s' % (config['target'], e)) - elif issubclass(klass, logging.handlers.SMTPHandler) and\ - 'mailhost' in config: - config['mailhost'] = self.as_tuple(config['mailhost']) - elif issubclass(klass, logging.handlers.SysLogHandler) and\ - 'address' in config: - config['address'] = self.as_tuple(config['address']) - factory = klass - kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) - try: - result = factory(**kwargs) - except TypeError as te: - if "'stream'" not in str(te): - raise - #The argument name changed from strm to stream - #Retry with old name. - #This is so that code can be used with older Python versions - #(e.g. by Django) - kwargs['strm'] = kwargs.pop('stream') - result = factory(**kwargs) - if formatter: - result.setFormatter(formatter) - if level is not None: - result.setLevel(logging._checkLevel(level)) - if filters: - self.add_filters(result, filters) - return result - - def add_handlers(self, logger, handlers): - """Add handlers to a logger from a list of names.""" - for h in handlers: - try: - logger.addHandler(self.config['handlers'][h]) - except StandardError as e: - raise ValueError('Unable to add handler %r: %s' % (h, e)) - - def common_logger_config(self, logger, config, incremental=False): - """ - Perform configuration which is common to root and non-root loggers. - """ - level = config.get('level', None) - if level is not None: - logger.setLevel(logging._checkLevel(level)) - if not incremental: - #Remove any existing handlers - for h in logger.handlers[:]: - logger.removeHandler(h) - handlers = config.get('handlers', None) - if handlers: - self.add_handlers(logger, handlers) - filters = config.get('filters', None) - if filters: - self.add_filters(logger, filters) - - def configure_logger(self, name, config, incremental=False): - """Configure a non-root logger from a dictionary.""" - logger = logging.getLogger(name) - self.common_logger_config(logger, config, incremental) - propagate = config.get('propagate', None) - if propagate is not None: - logger.propagate = propagate - - def configure_root(self, config, incremental=False): - """Configure a root logger from a dictionary.""" - root = logging.getLogger() - self.common_logger_config(root, config, incremental) - -dictConfigClass = DictConfigurator - -def dictConfig(config): - """Configure logging using a dictionary.""" - dictConfigClass(config).configure() - - -def listen(port=DEFAULT_LOGGING_CONFIG_PORT): - """ - Start up a socket server on the specified port, and listen for new - configurations. - - These will be sent as a file suitable for processing by fileConfig(). - Returns a Thread object on which you can call start() to start the server, - and which you can join() when appropriate. To stop the server, call - stopListening(). - """ - if not thread: - raise NotImplementedError("listen() needs threading to work") - - class ConfigStreamHandler(StreamRequestHandler): - """ - Handler for a logging configuration request. - - It expects a completely new logging configuration and uses fileConfig - to install it. - """ - def handle(self): - """ - Handle a request. - - Each request is expected to be a 4-byte length, packed using - struct.pack(">L", n), followed by the config file. - Uses fileConfig() to do the grunt work. - """ - import tempfile - try: - conn = self.connection - chunk = conn.recv(4) - if len(chunk) == 4: - slen = struct.unpack(">L", chunk)[0] - chunk = self.connection.recv(slen) - while len(chunk) < slen: - chunk = chunk + conn.recv(slen - len(chunk)) - try: - import json - d =json.loads(chunk) - assert isinstance(d, dict) - dictConfig(d) - except: - #Apply new configuration. - - file = cStringIO.StringIO(chunk) - try: - fileConfig(file) - except (KeyboardInterrupt, SystemExit): - raise - except: - traceback.print_exc() - if self.server.ready: - self.server.ready.set() - except socket.error as e: - if e.errno != RESET_ERROR: - raise - - class ConfigSocketReceiver(ThreadingTCPServer): - """ - A simple TCP socket-based logging config receiver. - """ - - allow_reuse_address = 1 - - def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, - handler=None, ready=None): - ThreadingTCPServer.__init__(self, (host, port), handler) - logging._acquireLock() - self.abort = 0 - logging._releaseLock() - self.timeout = 1 - self.ready = ready - - def serve_until_stopped(self): - import select - abort = 0 - while not abort: - rd, wr, ex = select.select([self.socket.fileno()], - [], [], - self.timeout) - if rd: - self.handle_request() - logging._acquireLock() - abort = self.abort - logging._releaseLock() - self.socket.close() - - class Server(threading.Thread): - - def __init__(self, rcvr, hdlr, port): - super(Server, self).__init__() - self.rcvr = rcvr - self.hdlr = hdlr - self.port = port - self.ready = threading.Event() - - def run(self): - server = self.rcvr(port=self.port, handler=self.hdlr, - ready=self.ready) - if self.port == 0: - self.port = server.server_address[1] - self.ready.set() - global _listener - logging._acquireLock() - _listener = server - logging._releaseLock() - server.serve_until_stopped() - - return Server(ConfigSocketReceiver, ConfigStreamHandler, port) - -def stopListening(): - """ - Stop the listening server which was created with a call to listen(). - """ - global _listener - logging._acquireLock() - try: - if _listener: - _listener.abort = 1 - _listener = None - finally: - logging._releaseLock() diff --git a/Monika After Story/game/python-packages/logging/handlers.py b/Monika After Story/game/python-packages/logging/handlers.py deleted file mode 100644 index e0b935c878..0000000000 --- a/Monika After Story/game/python-packages/logging/handlers.py +++ /dev/null @@ -1,1242 +0,0 @@ -# Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. -# -# Permission to use, copy, modify, and distribute this software and its -# documentation for any purpose and without fee is hereby granted, -# provided that the above copyright notice appear in all copies and that -# both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of Vinay Sajip -# not be used in advertising or publicity pertaining to distribution -# of the software without specific, written prior permission. -# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING -# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL -# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR -# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER -# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -""" -Additional handlers for the logging package for Python. The core package is -based on PEP 282 and comments thereto in comp.lang.python. - -Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. - -To use, simply 'import logging.handlers' and log away! -""" - -import errno, logging, socket, os, cPickle, struct, time, re -from stat import ST_DEV, ST_INO, ST_MTIME - -try: - import codecs -except ImportError: - codecs = None -try: - unicode - _unicode = True -except NameError: - _unicode = False - -# -# Some constants... -# - -DEFAULT_TCP_LOGGING_PORT = 9020 -DEFAULT_UDP_LOGGING_PORT = 9021 -DEFAULT_HTTP_LOGGING_PORT = 9022 -DEFAULT_SOAP_LOGGING_PORT = 9023 -SYSLOG_UDP_PORT = 514 -SYSLOG_TCP_PORT = 514 - -_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day - -class BaseRotatingHandler(logging.FileHandler): - """ - Base class for handlers that rotate log files at a certain point. - Not meant to be instantiated directly. Instead, use RotatingFileHandler - or TimedRotatingFileHandler. - """ - def __init__(self, filename, mode, encoding=None, delay=0): - """ - Use the specified filename for streamed logging - """ - if codecs is None: - encoding = None - logging.FileHandler.__init__(self, filename, mode, encoding, delay) - self.mode = mode - self.encoding = encoding - - def emit(self, record): - """ - Emit a record. - - Output the record to the file, catering for rollover as described - in doRollover(). - """ - try: - if self.shouldRollover(record): - self.doRollover() - logging.FileHandler.emit(self, record) - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - -class RotatingFileHandler(BaseRotatingHandler): - """ - Handler for logging to a set of files, which switches from one file - to the next when the current file reaches a certain size. - """ - def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0): - """ - Open the specified file and use it as the stream for logging. - - By default, the file grows indefinitely. You can specify particular - values of maxBytes and backupCount to allow the file to rollover at - a predetermined size. - - Rollover occurs whenever the current log file is nearly maxBytes in - length. If backupCount is >= 1, the system will successively create - new files with the same pathname as the base file, but with extensions - ".1", ".2" etc. appended to it. For example, with a backupCount of 5 - and a base file name of "app.log", you would get "app.log", - "app.log.1", "app.log.2", ... through to "app.log.5". The file being - written to is always "app.log" - when it gets filled up, it is closed - and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc. - exist, then they are renamed to "app.log.2", "app.log.3" etc. - respectively. - - If maxBytes is zero, rollover never occurs. - """ - # If rotation/rollover is wanted, it doesn't make sense to use another - # mode. If for example 'w' were specified, then if there were multiple - # runs of the calling application, the logs from previous runs would be - # lost if the 'w' is respected, because the log file would be truncated - # on each run. - if maxBytes > 0: - mode = 'a' - BaseRotatingHandler.__init__(self, filename, mode, encoding, delay) - self.maxBytes = maxBytes - self.backupCount = backupCount - - def doRollover(self): - """ - Do a rollover, as described in __init__(). - """ - if self.stream: - self.stream.close() - self.stream = None - if self.backupCount > 0: - for i in range(self.backupCount - 1, 0, -1): - sfn = "%s.%d" % (self.baseFilename, i) - dfn = "%s.%d" % (self.baseFilename, i + 1) - if os.path.exists(sfn): - #print "%s -> %s" % (sfn, dfn) - if os.path.exists(dfn): - os.remove(dfn) - os.rename(sfn, dfn) - dfn = self.baseFilename + ".1" - if os.path.exists(dfn): - os.remove(dfn) - # Issue 18940: A file may not have been created if delay is True. - if os.path.exists(self.baseFilename): - os.rename(self.baseFilename, dfn) - if not self.delay: - self.stream = self._open() - - def shouldRollover(self, record): - """ - Determine if rollover should occur. - - Basically, see if the supplied record would cause the file to exceed - the size limit we have. - """ - if self.stream is None: # delay was set... - self.stream = self._open() - if self.maxBytes > 0: # are we rolling over? - msg = "%s\n" % self.format(record) - self.stream.seek(0, 2) #due to non-posix-compliant Windows feature - if self.stream.tell() + len(msg) >= self.maxBytes: - return 1 - return 0 - -class TimedRotatingFileHandler(BaseRotatingHandler): - """ - Handler for logging to a file, rotating the log file at certain timed - intervals. - - If backupCount is > 0, when rollover is done, no more than backupCount - files are kept - the oldest ones are deleted. - """ - def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False): - BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) - self.when = when.upper() - self.backupCount = backupCount - self.utc = utc - # Calculate the real rollover interval, which is just the number of - # seconds between rollovers. Also set the filename suffix used when - # a rollover occurs. Current 'when' events supported: - # S - Seconds - # M - Minutes - # H - Hours - # D - Days - # midnight - roll over at midnight - # W{0-6} - roll over on a certain day; 0 - Monday - # - # Case of the 'when' specifier is not important; lower or upper case - # will work. - if self.when == 'S': - self.interval = 1 # one second - self.suffix = "%Y-%m-%d_%H-%M-%S" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$" - elif self.when == 'M': - self.interval = 60 # one minute - self.suffix = "%Y-%m-%d_%H-%M" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$" - elif self.when == 'H': - self.interval = 60 * 60 # one hour - self.suffix = "%Y-%m-%d_%H" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$" - elif self.when == 'D' or self.when == 'MIDNIGHT': - self.interval = 60 * 60 * 24 # one day - self.suffix = "%Y-%m-%d" - self.extMatch = r"^\d{4}-\d{2}-\d{2}$" - elif self.when.startswith('W'): - self.interval = 60 * 60 * 24 * 7 # one week - if len(self.when) != 2: - raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when) - if self.when[1] < '0' or self.when[1] > '6': - raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) - self.dayOfWeek = int(self.when[1]) - self.suffix = "%Y-%m-%d" - self.extMatch = r"^\d{4}-\d{2}-\d{2}$" - else: - raise ValueError("Invalid rollover interval specified: %s" % self.when) - - self.extMatch = re.compile(self.extMatch) - self.interval = self.interval * interval # multiply by units requested - if os.path.exists(filename): - t = os.stat(filename)[ST_MTIME] - else: - t = int(time.time()) - self.rolloverAt = self.computeRollover(t) - - def computeRollover(self, currentTime): - """ - Work out the rollover time based on the specified time. - """ - result = currentTime + self.interval - # If we are rolling over at midnight or weekly, then the interval is already known. - # What we need to figure out is WHEN the next interval is. In other words, - # if you are rolling over at midnight, then your base interval is 1 day, - # but you want to start that one day clock at midnight, not now. So, we - # have to fudge the rolloverAt value in order to trigger the first rollover - # at the right time. After that, the regular interval will take care of - # the rest. Note that this code doesn't care about leap seconds. :) - if self.when == 'MIDNIGHT' or self.when.startswith('W'): - # This could be done with less code, but I wanted it to be clear - if self.utc: - t = time.gmtime(currentTime) - else: - t = time.localtime(currentTime) - currentHour = t[3] - currentMinute = t[4] - currentSecond = t[5] - # r is the number of seconds left between now and midnight - r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 + - currentSecond) - result = currentTime + r - # If we are rolling over on a certain day, add in the number of days until - # the next rollover, but offset by 1 since we just calculated the time - # until the next day starts. There are three cases: - # Case 1) The day to rollover is today; in this case, do nothing - # Case 2) The day to rollover is further in the interval (i.e., today is - # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to - # next rollover is simply 6 - 2 - 1, or 3. - # Case 3) The day to rollover is behind us in the interval (i.e., today - # is day 5 (Saturday) and rollover is on day 3 (Thursday). - # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the - # number of days left in the current week (1) plus the number - # of days in the next week until the rollover day (3). - # The calculations described in 2) and 3) above need to have a day added. - # This is because the above time calculation takes us to midnight on this - # day, i.e. the start of the next day. - if self.when.startswith('W'): - day = t[6] # 0 is Monday - if day != self.dayOfWeek: - if day < self.dayOfWeek: - daysToWait = self.dayOfWeek - day - else: - daysToWait = 6 - day + self.dayOfWeek + 1 - newRolloverAt = result + (daysToWait * (60 * 60 * 24)) - if not self.utc: - dstNow = t[-1] - dstAtRollover = time.localtime(newRolloverAt)[-1] - if dstNow != dstAtRollover: - if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour - addend = -3600 - else: # DST bows out before next rollover, so we need to add an hour - addend = 3600 - newRolloverAt += addend - result = newRolloverAt - return result - - def shouldRollover(self, record): - """ - Determine if rollover should occur. - - record is not used, as we are just comparing times, but it is needed so - the method signatures are the same - """ - t = int(time.time()) - if t >= self.rolloverAt: - return 1 - #print "No need to rollover: %d, %d" % (t, self.rolloverAt) - return 0 - - def getFilesToDelete(self): - """ - Determine the files to delete when rolling over. - - More specific than the earlier method, which just used glob.glob(). - """ - dirName, baseName = os.path.split(self.baseFilename) - fileNames = os.listdir(dirName) - result = [] - prefix = baseName + "." - plen = len(prefix) - for fileName in fileNames: - if fileName[:plen] == prefix: - suffix = fileName[plen:] - if self.extMatch.match(suffix): - result.append(os.path.join(dirName, fileName)) - result.sort() - if len(result) < self.backupCount: - result = [] - else: - result = result[:len(result) - self.backupCount] - return result - - def doRollover(self): - """ - do a rollover; in this case, a date/time stamp is appended to the filename - when the rollover happens. However, you want the file to be named for the - start of the interval, not the current time. If there is a backup count, - then we have to get a list of matching filenames, sort them and remove - the one with the oldest suffix. - """ - if self.stream: - self.stream.close() - self.stream = None - # get the time that this sequence started at and make it a TimeTuple - currentTime = int(time.time()) - dstNow = time.localtime(currentTime)[-1] - t = self.rolloverAt - self.interval - if self.utc: - timeTuple = time.gmtime(t) - else: - timeTuple = time.localtime(t) - dstThen = timeTuple[-1] - if dstNow != dstThen: - if dstNow: - addend = 3600 - else: - addend = -3600 - timeTuple = time.localtime(t + addend) - dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) - if os.path.exists(dfn): - os.remove(dfn) - # Issue 18940: A file may not have been created if delay is True. - if os.path.exists(self.baseFilename): - os.rename(self.baseFilename, dfn) - if self.backupCount > 0: - for s in self.getFilesToDelete(): - os.remove(s) - if not self.delay: - self.stream = self._open() - newRolloverAt = self.computeRollover(currentTime) - while newRolloverAt <= currentTime: - newRolloverAt = newRolloverAt + self.interval - #If DST changes and midnight or weekly rollover, adjust for this. - if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: - dstAtRollover = time.localtime(newRolloverAt)[-1] - if dstNow != dstAtRollover: - if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour - addend = -3600 - else: # DST bows out before next rollover, so we need to add an hour - addend = 3600 - newRolloverAt += addend - self.rolloverAt = newRolloverAt - -class WatchedFileHandler(logging.FileHandler): - """ - A handler for logging to a file, which watches the file - to see if it has changed while in use. This can happen because of - usage of programs such as newsyslog and logrotate which perform - log file rotation. This handler, intended for use under Unix, - watches the file to see if it has changed since the last emit. - (A file has changed if its device or inode have changed.) - If it has changed, the old file stream is closed, and the file - opened to get a new stream. - - This handler is not appropriate for use under Windows, because - under Windows open files cannot be moved or renamed - logging - opens the files with exclusive locks - and so there is no need - for such a handler. Furthermore, ST_INO is not supported under - Windows; stat always returns zero for this value. - - This handler is based on a suggestion and patch by Chad J. - Schroeder. - """ - def __init__(self, filename, mode='a', encoding=None, delay=0): - logging.FileHandler.__init__(self, filename, mode, encoding, delay) - self.dev, self.ino = -1, -1 - self._statstream() - - def _statstream(self): - if self.stream: - sres = os.fstat(self.stream.fileno()) - self.dev, self.ino = sres[ST_DEV], sres[ST_INO] - - def emit(self, record): - """ - Emit a record. - - First check if the underlying file has changed, and if it - has, close the old stream and reopen the file to get the - current stream. - """ - # Reduce the chance of race conditions by stat'ing by path only - # once and then fstat'ing our new fd if we opened a new log stream. - # See issue #14632: Thanks to John Mulligan for the problem report - # and patch. - try: - # stat the file by path, checking for existence - sres = os.stat(self.baseFilename) - except OSError as err: - if err.errno == errno.ENOENT: - sres = None - else: - raise - # compare file system stat with that of our stream file handle - if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: - if self.stream is not None: - # we have an open file handle, clean it up - self.stream.flush() - self.stream.close() - self.stream = None # See Issue #21742: _open () might fail. - # open a new file handle and get new stat info from that fd - self.stream = self._open() - self._statstream() - logging.FileHandler.emit(self, record) - -class SocketHandler(logging.Handler): - """ - A handler class which writes logging records, in pickle format, to - a streaming socket. The socket is kept open across logging calls. - If the peer resets it, an attempt is made to reconnect on the next call. - The pickle which is sent is that of the LogRecord's attribute dictionary - (__dict__), so that the receiver does not need to have the logging module - installed in order to process the logging event. - - To unpickle the record at the receiving end into a LogRecord, use the - makeLogRecord function. - """ - - def __init__(self, host, port): - """ - Initializes the handler with a specific host address and port. - - The attribute 'closeOnError' is set to 1 - which means that if - a socket error occurs, the socket is silently closed and then - reopened on the next logging call. - """ - logging.Handler.__init__(self) - self.host = host - self.port = port - self.sock = None - self.closeOnError = 0 - self.retryTime = None - # - # Exponential backoff parameters. - # - self.retryStart = 1.0 - self.retryMax = 30.0 - self.retryFactor = 2.0 - - def makeSocket(self, timeout=1): - """ - A factory method which allows subclasses to define the precise - type of socket they want. - """ - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if hasattr(s, 'settimeout'): - s.settimeout(timeout) - s.connect((self.host, self.port)) - return s - - def createSocket(self): - """ - Try to create a socket, using an exponential backoff with - a max retry time. Thanks to Robert Olson for the original patch - (SF #815911) which has been slightly refactored. - """ - now = time.time() - # Either retryTime is None, in which case this - # is the first time back after a disconnect, or - # we've waited long enough. - if self.retryTime is None: - attempt = 1 - else: - attempt = (now >= self.retryTime) - if attempt: - try: - self.sock = self.makeSocket() - self.retryTime = None # next time, no delay before trying - except socket.error: - #Creation failed, so set the retry time and return. - if self.retryTime is None: - self.retryPeriod = self.retryStart - else: - self.retryPeriod = self.retryPeriod * self.retryFactor - if self.retryPeriod > self.retryMax: - self.retryPeriod = self.retryMax - self.retryTime = now + self.retryPeriod - - def send(self, s): - """ - Send a pickled string to the socket. - - This function allows for partial sends which can happen when the - network is busy. - """ - if self.sock is None: - self.createSocket() - #self.sock can be None either because we haven't reached the retry - #time yet, or because we have reached the retry time and retried, - #but are still unable to connect. - if self.sock: - try: - if hasattr(self.sock, "sendall"): - self.sock.sendall(s) - else: - sentsofar = 0 - left = len(s) - while left > 0: - sent = self.sock.send(s[sentsofar:]) - sentsofar = sentsofar + sent - left = left - sent - except socket.error: - self.sock.close() - self.sock = None # so we can call createSocket next time - - def makePickle(self, record): - """ - Pickles the record in binary format with a length prefix, and - returns it ready for transmission across the socket. - """ - ei = record.exc_info - if ei: - # just to get traceback text into record.exc_text ... - dummy = self.format(record) - record.exc_info = None # to avoid Unpickleable error - # See issue #14436: If msg or args are objects, they may not be - # available on the receiving end. So we convert the msg % args - # to a string, save it as msg and zap the args. - d = dict(record.__dict__) - d['msg'] = record.getMessage() - d['args'] = None - s = cPickle.dumps(d, 1) - if ei: - record.exc_info = ei # for next handler - slen = struct.pack(">L", len(s)) - return slen + s - - def handleError(self, record): - """ - Handle an error during logging. - - An error has occurred during logging. Most likely cause - - connection lost. Close the socket so that we can retry on the - next event. - """ - if self.closeOnError and self.sock: - self.sock.close() - self.sock = None #try to reconnect next time - else: - logging.Handler.handleError(self, record) - - def emit(self, record): - """ - Emit a record. - - Pickles the record and writes it to the socket in binary format. - If there is an error with the socket, silently drop the packet. - If there was a problem with the socket, re-establishes the - socket. - """ - try: - s = self.makePickle(record) - self.send(s) - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - - def close(self): - """ - Closes the socket. - """ - self.acquire() - try: - sock = self.sock - if sock: - self.sock = None - sock.close() - finally: - self.release() - logging.Handler.close(self) - -class DatagramHandler(SocketHandler): - """ - A handler class which writes logging records, in pickle format, to - a datagram socket. The pickle which is sent is that of the LogRecord's - attribute dictionary (__dict__), so that the receiver does not need to - have the logging module installed in order to process the logging event. - - To unpickle the record at the receiving end into a LogRecord, use the - makeLogRecord function. - - """ - def __init__(self, host, port): - """ - Initializes the handler with a specific host address and port. - """ - SocketHandler.__init__(self, host, port) - self.closeOnError = 0 - - def makeSocket(self): - """ - The factory method of SocketHandler is here overridden to create - a UDP socket (SOCK_DGRAM). - """ - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return s - - def send(self, s): - """ - Send a pickled string to a socket. - - This function no longer allows for partial sends which can happen - when the network is busy - UDP does not guarantee delivery and - can deliver packets out of sequence. - """ - if self.sock is None: - self.createSocket() - self.sock.sendto(s, (self.host, self.port)) - -class SysLogHandler(logging.Handler): - """ - A handler class which sends formatted logging records to a syslog - server. Based on Sam Rushing's syslog module: - http://www.nightmare.com/squirl/python-ext/misc/syslog.py - Contributed by Nicolas Untz (after which minor refactoring changes - have been made). - """ - - # from : - # ====================================================================== - # priorities/facilities are encoded into a single 32-bit quantity, where - # the bottom 3 bits are the priority (0-7) and the top 28 bits are the - # facility (0-big number). Both the priorities and the facilities map - # roughly one-to-one to strings in the syslogd(8) source code. This - # mapping is included in this file. - # - # priorities (these are ordered) - - LOG_EMERG = 0 # system is unusable - LOG_ALERT = 1 # action must be taken immediately - LOG_CRIT = 2 # critical conditions - LOG_ERR = 3 # error conditions - LOG_WARNING = 4 # warning conditions - LOG_NOTICE = 5 # normal but significant condition - LOG_INFO = 6 # informational - LOG_DEBUG = 7 # debug-level messages - - # facility codes - LOG_KERN = 0 # kernel messages - LOG_USER = 1 # random user-level messages - LOG_MAIL = 2 # mail system - LOG_DAEMON = 3 # system daemons - LOG_AUTH = 4 # security/authorization messages - LOG_SYSLOG = 5 # messages generated internally by syslogd - LOG_LPR = 6 # line printer subsystem - LOG_NEWS = 7 # network news subsystem - LOG_UUCP = 8 # UUCP subsystem - LOG_CRON = 9 # clock daemon - LOG_AUTHPRIV = 10 # security/authorization messages (private) - LOG_FTP = 11 # FTP daemon - - # other codes through 15 reserved for system use - LOG_LOCAL0 = 16 # reserved for local use - LOG_LOCAL1 = 17 # reserved for local use - LOG_LOCAL2 = 18 # reserved for local use - LOG_LOCAL3 = 19 # reserved for local use - LOG_LOCAL4 = 20 # reserved for local use - LOG_LOCAL5 = 21 # reserved for local use - LOG_LOCAL6 = 22 # reserved for local use - LOG_LOCAL7 = 23 # reserved for local use - - priority_names = { - "alert": LOG_ALERT, - "crit": LOG_CRIT, - "critical": LOG_CRIT, - "debug": LOG_DEBUG, - "emerg": LOG_EMERG, - "err": LOG_ERR, - "error": LOG_ERR, # DEPRECATED - "info": LOG_INFO, - "notice": LOG_NOTICE, - "panic": LOG_EMERG, # DEPRECATED - "warn": LOG_WARNING, # DEPRECATED - "warning": LOG_WARNING, - } - - facility_names = { - "auth": LOG_AUTH, - "authpriv": LOG_AUTHPRIV, - "cron": LOG_CRON, - "daemon": LOG_DAEMON, - "ftp": LOG_FTP, - "kern": LOG_KERN, - "lpr": LOG_LPR, - "mail": LOG_MAIL, - "news": LOG_NEWS, - "security": LOG_AUTH, # DEPRECATED - "syslog": LOG_SYSLOG, - "user": LOG_USER, - "uucp": LOG_UUCP, - "local0": LOG_LOCAL0, - "local1": LOG_LOCAL1, - "local2": LOG_LOCAL2, - "local3": LOG_LOCAL3, - "local4": LOG_LOCAL4, - "local5": LOG_LOCAL5, - "local6": LOG_LOCAL6, - "local7": LOG_LOCAL7, - } - - #The map below appears to be trivially lowercasing the key. However, - #there's more to it than meets the eye - in some locales, lowercasing - #gives unexpected results. See SF #1524081: in the Turkish locale, - #"INFO".lower() != "info" - priority_map = { - "DEBUG" : "debug", - "INFO" : "info", - "WARNING" : "warning", - "ERROR" : "error", - "CRITICAL" : "critical" - } - - def __init__(self, address=('localhost', SYSLOG_UDP_PORT), - facility=LOG_USER, socktype=None): - """ - Initialize a handler. - - If address is specified as a string, a UNIX socket is used. To log to a - local syslogd, "SysLogHandler(address="/dev/log")" can be used. - If facility is not specified, LOG_USER is used. If socktype is - specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific - socket type will be used. For Unix sockets, you can also specify a - socktype of None, in which case socket.SOCK_DGRAM will be used, falling - back to socket.SOCK_STREAM. - """ - logging.Handler.__init__(self) - - self.address = address - self.facility = facility - self.socktype = socktype - - if isinstance(address, basestring): - self.unixsocket = 1 - self._connect_unixsocket(address) - else: - self.unixsocket = False - if socktype is None: - socktype = socket.SOCK_DGRAM - host, port = address - ress = socket.getaddrinfo(host, port, 0, socktype) - if not ress: - raise socket.error("getaddrinfo returns an empty list") - for res in ress: - af, socktype, proto, _, sa = res - err = sock = None - try: - sock = socket.socket(af, socktype, proto) - if socktype == socket.SOCK_STREAM: - sock.connect(sa) - break - except socket.error as exc: - err = exc - if sock is not None: - sock.close() - if err is not None: - raise err - self.socket = sock - self.socktype = socktype - - def _connect_unixsocket(self, address): - use_socktype = self.socktype - if use_socktype is None: - use_socktype = socket.SOCK_DGRAM - self.socket = socket.socket(socket.AF_UNIX, use_socktype) - try: - self.socket.connect(address) - # it worked, so set self.socktype to the used type - self.socktype = use_socktype - except socket.error: - self.socket.close() - if self.socktype is not None: - # user didn't specify falling back, so fail - raise - use_socktype = socket.SOCK_STREAM - self.socket = socket.socket(socket.AF_UNIX, use_socktype) - try: - self.socket.connect(address) - # it worked, so set self.socktype to the used type - self.socktype = use_socktype - except socket.error: - self.socket.close() - raise - - # curious: when talking to the unix-domain '/dev/log' socket, a - # zero-terminator seems to be required. this string is placed - # into a class variable so that it can be overridden if - # necessary. - log_format_string = '<%d>%s\000' - - def encodePriority(self, facility, priority): - """ - Encode the facility and priority. You can pass in strings or - integers - if strings are passed, the facility_names and - priority_names mapping dictionaries are used to convert them to - integers. - """ - if isinstance(facility, basestring): - facility = self.facility_names[facility] - if isinstance(priority, basestring): - priority = self.priority_names[priority] - return (facility << 3) | priority - - def close(self): - """ - Closes the socket. - """ - self.acquire() - try: - if self.unixsocket: - self.socket.close() - finally: - self.release() - logging.Handler.close(self) - - def mapPriority(self, levelName): - """ - Map a logging level name to a key in the priority_names map. - This is useful in two scenarios: when custom levels are being - used, and in the case where you can't do a straightforward - mapping by lowercasing the logging level name because of locale- - specific issues (see SF #1524081). - """ - return self.priority_map.get(levelName, "warning") - - def emit(self, record): - """ - Emit a record. - - The record is formatted, and then sent to the syslog server. If - exception information is present, it is NOT sent to the server. - """ - try: - msg = self.format(record) + '\000' - """ - We need to convert record level to lowercase, maybe this will - change in the future. - """ - prio = '<%d>' % self.encodePriority(self.facility, - self.mapPriority(record.levelname)) - # Message is a string. Convert to bytes as required by RFC 5424 - if type(msg) is unicode: - msg = msg.encode('utf-8') - msg = prio + msg - if self.unixsocket: - try: - self.socket.send(msg) - except socket.error: - self.socket.close() # See issue 17981 - self._connect_unixsocket(self.address) - self.socket.send(msg) - elif self.socktype == socket.SOCK_DGRAM: - self.socket.sendto(msg, self.address) - else: - self.socket.sendall(msg) - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - -class SMTPHandler(logging.Handler): - """ - A handler class which sends an SMTP email for each logging event. - """ - def __init__(self, mailhost, fromaddr, toaddrs, subject, - credentials=None, secure=None): - """ - Initialize the handler. - - Initialize the instance with the from and to addresses and subject - line of the email. To specify a non-standard SMTP port, use the - (host, port) tuple format for the mailhost argument. To specify - authentication credentials, supply a (username, password) tuple - for the credentials argument. To specify the use of a secure - protocol (TLS), pass in a tuple for the secure argument. This will - only be used when authentication credentials are supplied. The tuple - will be either an empty tuple, or a single-value tuple with the name - of a keyfile, or a 2-value tuple with the names of the keyfile and - certificate file. (This tuple is passed to the `starttls` method). - """ - logging.Handler.__init__(self) - if isinstance(mailhost, (list, tuple)): - self.mailhost, self.mailport = mailhost - else: - self.mailhost, self.mailport = mailhost, None - if isinstance(credentials, (list, tuple)): - self.username, self.password = credentials - else: - self.username = None - self.fromaddr = fromaddr - if isinstance(toaddrs, basestring): - toaddrs = [toaddrs] - self.toaddrs = toaddrs - self.subject = subject - self.secure = secure - self._timeout = 5.0 - - def getSubject(self, record): - """ - Determine the subject for the email. - - If you want to specify a subject line which is record-dependent, - override this method. - """ - return self.subject - - def emit(self, record): - """ - Emit a record. - - Format the record and send it to the specified addressees. - """ - try: - import smtplib - from email.utils import formatdate - port = self.mailport - if not port: - port = smtplib.SMTP_PORT - smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) - msg = self.format(record) - msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( - self.fromaddr, - ",".join(self.toaddrs), - self.getSubject(record), - formatdate(), msg) - if self.username: - if self.secure is not None: - smtp.ehlo() - smtp.starttls(*self.secure) - smtp.ehlo() - smtp.login(self.username, self.password) - smtp.sendmail(self.fromaddr, self.toaddrs, msg) - smtp.quit() - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - -class NTEventLogHandler(logging.Handler): - """ - A handler class which sends events to the NT Event Log. Adds a - registry entry for the specified application name. If no dllname is - provided, win32service.pyd (which contains some basic message - placeholders) is used. Note that use of these placeholders will make - your event logs big, as the entire message source is held in the log. - If you want slimmer logs, you have to pass in the name of your own DLL - which contains the message definitions you want to use in the event log. - """ - def __init__(self, appname, dllname=None, logtype="Application"): - logging.Handler.__init__(self) - try: - import win32evtlogutil, win32evtlog - self.appname = appname - self._welu = win32evtlogutil - if not dllname: - dllname = os.path.split(self._welu.__file__) - dllname = os.path.split(dllname[0]) - dllname = os.path.join(dllname[0], r'win32service.pyd') - self.dllname = dllname - self.logtype = logtype - self._welu.AddSourceToRegistry(appname, dllname, logtype) - self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE - self.typemap = { - logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, - logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, - logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE, - logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, - logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, - } - except ImportError: - print("The Python Win32 extensions for NT (service, event "\ - "logging) appear not to be available.") - self._welu = None - - def getMessageID(self, record): - """ - Return the message ID for the event record. If you are using your - own messages, you could do this by having the msg passed to the - logger being an ID rather than a formatting string. Then, in here, - you could use a dictionary lookup to get the message ID. This - version returns 1, which is the base message ID in win32service.pyd. - """ - return 1 - - def getEventCategory(self, record): - """ - Return the event category for the record. - - Override this if you want to specify your own categories. This version - returns 0. - """ - return 0 - - def getEventType(self, record): - """ - Return the event type for the record. - - Override this if you want to specify your own types. This version does - a mapping using the handler's typemap attribute, which is set up in - __init__() to a dictionary which contains mappings for DEBUG, INFO, - WARNING, ERROR and CRITICAL. If you are using your own levels you will - either need to override this method or place a suitable dictionary in - the handler's typemap attribute. - """ - return self.typemap.get(record.levelno, self.deftype) - - def emit(self, record): - """ - Emit a record. - - Determine the message ID, event category and event type. Then - log the message in the NT event log. - """ - if self._welu: - try: - id = self.getMessageID(record) - cat = self.getEventCategory(record) - type = self.getEventType(record) - msg = self.format(record) - self._welu.ReportEvent(self.appname, id, cat, type, [msg]) - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - - def close(self): - """ - Clean up this handler. - - You can remove the application name from the registry as a - source of event log entries. However, if you do this, you will - not be able to see the events as you intended in the Event Log - Viewer - it needs to be able to access the registry to get the - DLL name. - """ - #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) - logging.Handler.close(self) - -class HTTPHandler(logging.Handler): - """ - A class which sends records to a Web server, using either GET or - POST semantics. - """ - def __init__(self, host, url, method="GET"): - """ - Initialize the instance with the host, the request URL, and the method - ("GET" or "POST") - """ - logging.Handler.__init__(self) - method = method.upper() - if method not in ["GET", "POST"]: - raise ValueError("method must be GET or POST") - self.host = host - self.url = url - self.method = method - - def mapLogRecord(self, record): - """ - Default implementation of mapping the log record into a dict - that is sent as the CGI data. Overwrite in your class. - Contributed by Franz Glasner. - """ - return record.__dict__ - - def emit(self, record): - """ - Emit a record. - - Send the record to the Web server as a percent-encoded dictionary - """ - try: - import httplib, urllib - host = self.host - h = httplib.HTTP(host) - url = self.url - data = urllib.urlencode(self.mapLogRecord(record)) - if self.method == "GET": - if (url.find('?') >= 0): - sep = '&' - else: - sep = '?' - url = url + "%c%s" % (sep, data) - h.putrequest(self.method, url) - # support multiple hosts on one IP address... - # need to strip optional :port from host, if present - i = host.find(":") - if i >= 0: - host = host[:i] - h.putheader("Host", host) - if self.method == "POST": - h.putheader("Content-type", - "application/x-www-form-urlencoded") - h.putheader("Content-length", str(len(data))) - h.endheaders(data if self.method == "POST" else None) - h.getreply() #can't do anything with the result - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handleError(record) - -class BufferingHandler(logging.Handler): - """ - A handler class which buffers logging records in memory. Whenever each - record is added to the buffer, a check is made to see if the buffer should - be flushed. If it should, then flush() is expected to do what's needed. - """ - def __init__(self, capacity): - """ - Initialize the handler with the buffer size. - """ - logging.Handler.__init__(self) - self.capacity = capacity - self.buffer = [] - - def shouldFlush(self, record): - """ - Should the handler flush its buffer? - - Returns true if the buffer is up to capacity. This method can be - overridden to implement custom flushing strategies. - """ - return (len(self.buffer) >= self.capacity) - - def emit(self, record): - """ - Emit a record. - - Append the record. If shouldFlush() tells us to, call flush() to process - the buffer. - """ - self.buffer.append(record) - if self.shouldFlush(record): - self.flush() - - def flush(self): - """ - Override to implement custom flushing behaviour. - - This version just zaps the buffer to empty. - """ - self.acquire() - try: - self.buffer = [] - finally: - self.release() - - def close(self): - """ - Close the handler. - - This version just flushes and chains to the parent class' close(). - """ - try: - self.flush() - finally: - logging.Handler.close(self) - -class MemoryHandler(BufferingHandler): - """ - A handler class which buffers logging records in memory, periodically - flushing them to a target handler. Flushing occurs whenever the buffer - is full, or when an event of a certain severity or greater is seen. - """ - def __init__(self, capacity, flushLevel=logging.ERROR, target=None): - """ - Initialize the handler with the buffer size, the level at which - flushing should occur and an optional target. - - Note that without a target being set either here or via setTarget(), - a MemoryHandler is no use to anyone! - """ - BufferingHandler.__init__(self, capacity) - self.flushLevel = flushLevel - self.target = target - - def shouldFlush(self, record): - """ - Check for buffer full or a record at the flushLevel or higher. - """ - return (len(self.buffer) >= self.capacity) or \ - (record.levelno >= self.flushLevel) - - def setTarget(self, target): - """ - Set the target handler for this handler. - """ - self.target = target - - def flush(self): - """ - For a MemoryHandler, flushing means just sending the buffered - records to the target, if there is one. Override if you want - different behaviour. - """ - self.acquire() - try: - if self.target: - for record in self.buffer: - self.target.handle(record) - self.buffer = [] - finally: - self.release() - - def close(self): - """ - Flush, set the target to None and lose the buffer. - """ - try: - self.flush() - finally: - self.acquire() - try: - self.target = None - BufferingHandler.close(self) - finally: - self.release() From 33c0f1ffcb11c308d8954154231c4384cd70f77d Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 23:12:17 -0500 Subject: [PATCH 054/180] better --- Monika After Story/game/0utils.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/0utils.rpy b/Monika After Story/game/0utils.rpy index 0393609a12..11236bcb33 100644 --- a/Monika After Story/game/0utils.rpy +++ b/Monika After Story/game/0utils.rpy @@ -13,7 +13,7 @@ python early in mas_logging: import re #Thanks python... - from logging import handlers as loghandlers + import logging.handlers as loghandlers # log tags LT_INFO = "info" From 0ac7b2ecce94b80eda1e1ce2075252397b505f68 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 23:17:28 -0500 Subject: [PATCH 055/180] uncache MAS --- .github/workflows/mas_check.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 96d1b30a25..8f51cc0f5b 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -45,15 +45,15 @@ jobs: mv ${renpysdk/.tar.bz2/} renpy # get/download base mas - - name: cache base MAS - id: cache-mas - uses: actions/cache@v2 - with: - path: mas0105/ - key: ${{ runner.os }}-mas + # - name: cache base MAS + # id: cache-mas + # uses: actions/cache@v2 + # with: + # path: mas0105/ + # key: ${{ runner.os }}-mas - name: Download base MAS - if: steps.cache-mas.outputs.cache-hit != 'true' + # if: steps.cache-mas.outputs.cache-hit != 'true' run: | wget https://s3-us-west-2.amazonaws.com/monika-after-story/ddlc/mas.zip mkdir mas0105 From b2a5a36d747d1e4bbee7025492a63f1380ab5723 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sun, 20 Feb 2022 23:19:20 -0500 Subject: [PATCH 056/180] rm unicode --- Monika After Story/game/zz_submods.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index ff69c97eaa..a1f3772728 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -64,7 +64,7 @@ init -991 python in mas_submod_utils: FB_VERS_STR = "0.0.0" #Regular expression representing a valid author and name - AN_REGEXP = re.compile(ur'^[ a-zA-Z_\u00a0-\ufffd][ 0-9a-zA-Z_\u00a0-\ufffd]*$') + AN_REGEXP = re.compile(r'^[ a-zA-Z_\u00a0-\ufffd][ 0-9a-zA-Z_\u00a0-\ufffd]*$') def __init__( self, From 3811f3b21d9b52da62ccfdfacd17b2670fced14a Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 01:23:12 -0500 Subject: [PATCH 057/180] clean up more libs + no more fast/slowIO --- Monika After Story/game/definitions.rpy | 10 +- Monika After Story/game/event-handler.rpy | 8 +- Monika After Story/game/pong.rpy | 107 +++++++++--------- Monika After Story/game/script-python.rpy | 8 +- Monika After Story/game/zz_backgrounds.rpy | 3 +- Monika After Story/game/zz_dockingstation.rpy | 20 ++-- Monika After Story/game/zz_spritejsons.rpy | 6 +- 7 files changed, 80 insertions(+), 82 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 0c5792f4d6..5f16fb49b0 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -4627,7 +4627,7 @@ init -100 python in mas_utils: import random import os import math - from cStringIO import StringIO as fastIO + from io import StringIO __secInDay = 24 * 60 * 60 @@ -5138,9 +5138,9 @@ init -100 python in mas_utils: (Default: None) RETURNS: - a cStringIO buffer of the random blob + a StringIO buffer of the random blob """ - data = fastIO() + data = StringIO() _byte_count = 0 curr_state = None @@ -5170,9 +5170,9 @@ init -100 python in mas_utils: size - size in bytes of the blob to make RETURNS: - a cStringIO buffer of the random blob + a StringIO buffer of the random blob """ - data = fastIO() + data = StringIO() _byte_limit = 4 * (1024**2) # 4MB while size > 0: diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index b163fd52c6..55e5b82fa8 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -30,7 +30,7 @@ init -999 python in mas_ev_data_ver: # must be before -900 so we can use in persistent backup/cleanup # need to use real lists and dicts here - import __builtin__ + import builtins # special store dedicated to verification of Event-based data import datetime @@ -42,11 +42,11 @@ init -999 python in mas_ev_data_ver: def _verify_dict(val, allow_none=True): - return _verify_item(val, __builtin__.dict, allow_none) + return _verify_item(val, builtins.dict, allow_none) def _verify_list(val, allow_none=True): - return _verify_item(val, __builtin__.list, allow_none) + return _verify_item(val, builtins.list, allow_none) def _verify_dt(val, allow_none=True): @@ -98,7 +98,7 @@ init -999 python in mas_ev_data_ver: if val is None: return allow_none - return isinstance(val, __builtin__.list) or isinstance(val, tuple) + return isinstance(val, builtins.list) or isinstance(val, tuple) def _verify_tuli_nn(val): diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index 1575344639..fa5be203d8 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -7,53 +7,54 @@ default persistent._mas_pm_ever_let_monika_win_on_purpose = False # the day at which the difficulty change was initiated default persistent._mas_pong_difficulty_change_next_game_date = datetime.date.today() -define PONG_DIFFICULTY_CHANGE_ON_WIN = +1 -define PONG_DIFFICULTY_CHANGE_ON_LOSS = -1 -define PONG_DIFFICULTY_POWERUP = +5 -define PONG_DIFFICULTY_POWERDOWN = -5 -define PONG_PONG_DIFFICULTY_POWERDOWNBIG = -10 +init -5 python: + PONG_DIFFICULTY_CHANGE_ON_WIN = +1 + PONG_DIFFICULTY_CHANGE_ON_LOSS = -1 + PONG_DIFFICULTY_POWERUP = +5 + PONG_DIFFICULTY_POWERDOWN = -5 + PONG_PONG_DIFFICULTY_POWERDOWNBIG = -10 #Triggering the same response twice in a row leads to a different response, not all responses reset this (on purpose) -define PONG_MONIKA_RESPONSE_NONE = 0 -define PONG_MONIKA_RESPONSE_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 1 -define PONG_MONIKA_RESPONSE_SECOND_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 2 -define PONG_MONIKA_RESPONSE_WIN_LONG_GAME = 3 -define PONG_MONIKA_RESPONSE_WIN_SHORT_GAME = 4 -define PONG_MONIKA_RESPONSE_WIN_TRICKSHOT = 5 -define PONG_MONIKA_RESPONSE_WIN_EASY_GAME = 6 -define PONG_MONIKA_RESPONSE_WIN_MEDIUM_GAME = 7 -define PONG_MONIKA_RESPONSE_WIN_HARD_GAME = 8 -define PONG_MONIKA_RESPONSE_WIN_EXPERT_GAME = 9 -define PONG_MONIKA_RESPONSE_WIN_EXTREME_GAME = 10 -define PONG_MONIKA_RESPONSE_LOSE_WITHOUT_HITTING_BALL = 11 -define PONG_MONIKA_RESPONSE_LOSE_TRICKSHOT = 12 -define PONG_MONIKA_RESPONSE_LOSE_LONG_GAME = 13 -define PONG_MONIKA_RESPONSE_LOSE_SHORT_GAME = 14 -define PONG_MONIKA_RESPONSE_LOSE_EASY_GAME = 15 -define PONG_MONIKA_RESPONSE_LOSE_MEDIUM_GAME = 16 -define PONG_MONIKA_RESPONSE_LOSE_HARD_GAME = 17 -define PONG_MONIKA_RESPONSE_LOSE_EXPERT_GAME = 18 -define PONG_MONIKA_RESPONSE_LOSE_EXTREME_GAME = 19 - -define pong_monika_last_response_id = PONG_MONIKA_RESPONSE_NONE - -define played_pong_this_session = False -define mas_pong_taking_break = False -define player_lets_monika_win_on_purpose = False -define instant_loss_streak_counter = 0 -define loss_streak_counter = 0 -define win_streak_counter = 0 -define lose_on_purpose = False -define monika_asks_to_go_easy = False - -# Need to be set before every game and be accessible outside the class -define ball_paddle_bounces = 0 -define powerup_value_this_game = 0 -define instant_loss_streak_counter_before = 0 -define loss_streak_counter_before = 0 -define win_streak_counter_before = 0 -define pong_difficulty_before = 0 -define pong_angle_last_shot = 0.0 + PONG_MONIKA_RESPONSE_NONE = 0 + PONG_MONIKA_RESPONSE_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 1 + PONG_MONIKA_RESPONSE_SECOND_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 2 + PONG_MONIKA_RESPONSE_WIN_LONG_GAME = 3 + PONG_MONIKA_RESPONSE_WIN_SHORT_GAME = 4 + PONG_MONIKA_RESPONSE_WIN_TRICKSHOT = 5 + PONG_MONIKA_RESPONSE_WIN_EASY_GAME = 6 + PONG_MONIKA_RESPONSE_WIN_MEDIUM_GAME = 7 + PONG_MONIKA_RESPONSE_WIN_HARD_GAME = 8 + PONG_MONIKA_RESPONSE_WIN_EXPERT_GAME = 9 + PONG_MONIKA_RESPONSE_WIN_EXTREME_GAME = 10 + PONG_MONIKA_RESPONSE_LOSE_WITHOUT_HITTING_BALL = 11 + PONG_MONIKA_RESPONSE_LOSE_TRICKSHOT = 12 + PONG_MONIKA_RESPONSE_LOSE_LONG_GAME = 13 + PONG_MONIKA_RESPONSE_LOSE_SHORT_GAME = 14 + PONG_MONIKA_RESPONSE_LOSE_EASY_GAME = 15 + PONG_MONIKA_RESPONSE_LOSE_MEDIUM_GAME = 16 + PONG_MONIKA_RESPONSE_LOSE_HARD_GAME = 17 + PONG_MONIKA_RESPONSE_LOSE_EXPERT_GAME = 18 + PONG_MONIKA_RESPONSE_LOSE_EXTREME_GAME = 19 + + pong_monika_last_response_id = PONG_MONIKA_RESPONSE_NONE + + played_pong_this_session = False + mas_pong_taking_break = False + player_lets_monika_win_on_purpose = False + instant_loss_streak_counter = 0 + loss_streak_counter = 0 + win_streak_counter = 0 + lose_on_purpose = False + monika_asks_to_go_easy = False + + # Need to be set before every game and be accessible outside the class + ball_paddle_bounces = 0 + powerup_value_this_game = 0 + instant_loss_streak_counter_before = 0 + loss_streak_counter_before = 0 + win_streak_counter_before = 0 + pong_difficulty_before = 0 + pong_angle_last_shot = 0.0 init: @@ -320,6 +321,9 @@ init: # This draws a paddle, and checks for bounces. def paddle(px, py, hotside, is_computer): + global win_streak_counter + global loss_streak_counter + global instant_loss_streak_counter # Render the paddle image. We give it an 1280x720 area # to render into, knowing that images will render smaller. @@ -405,21 +409,17 @@ init: # Check for a winner. if self.bx < -200: - if self.winner == None: - global loss_streak_counter loss_streak_counter += 1 + if ball_paddle_bounces <= 1: - global instant_loss_streak_counter instant_loss_streak_counter += 1 else: - global instant_loss_streak_counter instant_loss_streak_counter = 0 - global win_streak_counter - win_streak_counter = 0; + win_streak_counter = 0 self.winner = "monika" # Needed to ensure that event is called, noticing @@ -428,20 +428,17 @@ init: elif self.bx > self.COURT_WIDTH + 200: + if self.winner == None: - global win_streak_counter win_streak_counter += 1; - global loss_streak_counter loss_streak_counter = 0 #won't reset if Monika misses the first hit if ball_paddle_bounces > 1: - global instant_loss_streak_counter instant_loss_streak_counter = 0 self.winner = "player" - renpy.timeout(0) # Ask that we be re-rendered ASAP, so we can show the next diff --git a/Monika After Story/game/script-python.rpy b/Monika After Story/game/script-python.rpy index d3e13a8c4a..95b9f471b5 100644 --- a/Monika After Story/game/script-python.rpy +++ b/Monika After Story/game/script-python.rpy @@ -1024,11 +1024,11 @@ init -1 python in mas_ptod: IN: cmd - the command to write to the console """ + global cn_line, cn_cmd, state, stack_level + if state == STATE_OFF: return - global cn_line, cn_cmd, state, stack_level - if state == STATE_MULTI: # this is bad! You should execute the previous command first! # in this case, we will clear your current command and reset @@ -1203,14 +1203,14 @@ init -1 python in mas_ptod: locals here. If None, then we use the local_ctx. """ + global cn_cmd, cn_line, state, stack_level, blk_cmd + if state == STATE_OFF: return if context is None: context = local_ctx - global cn_cmd, cn_line, state, stack_level, blk_cmd - ################### setup some initial conditions ################ # block mode just means we are in a block diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index 200ac1685c..0a9d96fbfc 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -2855,8 +2855,9 @@ init 800 python: **kwargs: Additional kwargs to send to the prog points """ + global mas_current_background + if _background != mas_current_background: - global mas_current_background old_background = mas_current_background mas_current_background = _background mas_current_background.entry(old_background, **kwargs) diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 19dc663b3d..962e9e302b 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -71,8 +71,8 @@ init -45 python: """ import hashlib # sha256 signatures import base64 # "packing" shipments involve base64 - from StringIO import StringIO as slowIO - from cStringIO import StringIO as fastIO + from StringIO import StringIO as StringIO + from io import StringIO import store.mas_utils as mas_utils # logging @@ -345,7 +345,7 @@ init -45 python: """ box = None try: - box = self.fastIO() + box = self.StringIO() return (box, self._pack(contents, box, True, pkg_slip)) @@ -482,8 +482,8 @@ init -45 python: ### we have a package, lets unpack it if keep_contents: - # use slowIO since we dont know contents unpacked - contents = slowIO() + # use StringIO since we dont know contents unpacked + contents = StringIO() # we always want a package slip in this case # we only want to unpack if we are keeping contents @@ -591,7 +591,7 @@ init -45 python: # internalize contents so we can do proper file closing if contents is None: - _contents = self.slowIO() + _contents = self.StringIO() else: _contents = contents @@ -722,7 +722,7 @@ init -45 python: contents = None try: # NOTE: we use regular StringIO in case of unicode - contents = self.slowIO() + contents = self.StringIO() _pkg_slip = self._unpack( package, @@ -1224,7 +1224,7 @@ init 200 python in mas_dockstat: import store.mas_greetings as mas_greetings import store.mas_ics as mas_ics import store.evhand as evhand - from cStringIO import StringIO as fastIO + from cStringIO import StringIO as StringIO import codecs import re import os @@ -1486,7 +1486,7 @@ init 200 python in mas_dockstat: ### other stuff we need # inital buffer - moni_buffer = fastIO() + moni_buffer = StringIO() moni_buffer = codecs.getwriter("utf8")(moni_buffer) # number deliemter @@ -1550,7 +1550,7 @@ init 200 python in mas_dockstat: moni_buffer, blocksize ) - moni_tbuffer = fastIO() + moni_tbuffer = StringIO() moni_tbuffer = codecs.getwriter("utf8")(moni_tbuffer) moni_tbuffer.write(str(lines) + NUM_DELIM) for _line in moni_buffer_iter: diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index 3e707fab88..abee7520cb 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -388,7 +388,7 @@ default persistent._mas_sprites_json_gifted_sprites = {} init -21 python in mas_sprites_json: - import __builtin__ + import builtins import json import store import store.mas_utils as mas_utils @@ -460,8 +460,8 @@ init -21 python in mas_sprites_json: adapter_ctor=SpriteJsonLogAdapter ) - py_list = __builtin__.list - py_dict = __builtin__.dict + py_list = builtins.list + py_dict = builtins.dict sprite_station = store.MASDockingStation( renpy.config.basedir + "/game/mod_assets/monika/j/" From 6c77786f59e310dbb393b56febb721f80d4df80c Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 10:55:06 -0500 Subject: [PATCH 058/180] `global` fixes + add DDLC's gui.rpy + axe archive deps --- Monika After Story/game/chess.rpy | 2 +- Monika After Story/game/gui.rpy | 463 ++++++++++++++++++ Monika After Story/game/pong.rpy | 7 +- Monika After Story/game/splash.rpy | 12 +- Monika After Story/game/zz_dockingstation.rpy | 1 - 5 files changed, 473 insertions(+), 12 deletions(-) create mode 100644 Monika After Story/game/gui.rpy diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 34391df34b..a990490f6e 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -1849,7 +1849,7 @@ init python: import random import pygame import threading - import StringIO + from io import StringIO import os #Only add the chess_games folder if we can even do chess diff --git a/Monika After Story/game/gui.rpy b/Monika After Story/game/gui.rpy new file mode 100644 index 0000000000..791e772c18 --- /dev/null +++ b/Monika After Story/game/gui.rpy @@ -0,0 +1,463 @@ + + + + + + + + + + +init -2 python: + gui.init(1280, 720) + + + + + + + +define -2 gui.hover_sound = "gui/sfx/hover.ogg" +define -2 gui.activate_sound = "gui/sfx/select.ogg" +define -2 gui.activate_sound_glitch = "gui/sfx/select_glitch.ogg" + + + + + + +define -2 gui.accent_color = '#ffffff' + + +define -2 gui.idle_color = '#aaaaaa' + + + +define -2 gui.idle_small_color = '#333' + + +define -2 gui.hover_color = '#cc6699' + + + +define -2 gui.selected_color = '#bb5588' + + +define -2 gui.insensitive_color = '#aaaaaa7f' + + + +define -2 gui.muted_color = '#6666a3' +define -2 gui.hover_muted_color = '#9999c1' + + +define -2 gui.text_color = '#ffffff' +define -2 gui.interface_text_color = '#ffffff' + + + + + +define -2 gui.default_font = "gui/font/Aller_Rg.ttf" + + +define -2 gui.name_font = "gui/font/RifficFree-Bold.ttf" + + +define -2 gui.interface_font = "gui/font/Aller_Rg.ttf" + + +define -2 gui.text_size = 24 + + +define -2 gui.name_text_size = 24 + + +define -2 gui.interface_text_size = 24 + + +define -2 gui.label_text_size = 28 + + +define -2 gui.notify_text_size = 16 + + +define -2 gui.title_text_size = 38 + + + + + +define -2 gui.main_menu_background = "menu_bg" +define -2 gui.game_menu_background = "game_menu_bg" + + +define -2 gui.show_name = False + + + + + + + + +define -2 gui.textbox_height = 182 + + + +define -2 gui.textbox_yalign = 0.99 + + + + +define -2 gui.name_xpos = 350 +define -2 gui.name_ypos = -3 + + + +define -2 gui.name_xalign = 0.5 + + + +define -2 gui.namebox_width = 168 +define -2 gui.namebox_height = 39 + + + +define -2 gui.namebox_borders = Borders(5, 5, 5, 2) + + + +define -2 gui.namebox_tile = False + + + + + +define -2 gui.text_xpos = 268 +define -2 gui.text_ypos = 62 + + +define -2 gui.text_width = 744 + + + +define -2 gui.text_xalign = 0.0 + + + + + + + + +define -2 gui.button_width = None +define -2 gui.button_height = 36 + + +define -2 gui.button_borders = Borders(4, 4, 4, 4) + + + +define -2 gui.button_tile = False + + +define -2 gui.button_text_font = gui.interface_font + + +define -2 gui.button_text_size = gui.interface_text_size + + +define -2 gui.button_text_idle_color = gui.idle_color +define -2 gui.button_text_hover_color = gui.hover_color +define -2 gui.button_text_selected_color = gui.selected_color +define -2 gui.button_text_insensitive_color = gui.insensitive_color + + + +define -2 gui.button_text_xalign = 0.0 + + + + + + + + +define -2 gui.radio_button_borders = Borders(28, 4, 4, 4) + +define -2 gui.check_button_borders = Borders(28, 4, 4, 4) + +define -2 gui.confirm_button_text_xalign = 0.5 + +define -2 gui.page_button_borders = Borders(10, 4, 10, 4) + + +define -2 gui.quick_button_text_size = 14 +define -2 gui.quick_button_text_idle_color = "#522" +define -2 gui.quick_button_text_hover_color = "#fcc" +define -2 gui.quick_button_text_selected_color = gui.accent_color +define -2 gui.quick_button_text_insensitive_color = "#a66" + + + + + + + + + + + + +define -2 gui.choice_button_width = 420 +define -2 gui.choice_button_height = None +define -2 gui.choice_button_tile = False +define -2 gui.choice_button_borders = Borders(100, 5, 100, 5) +define -2 gui.choice_button_text_font = gui.default_font +define -2 gui.choice_button_text_size = gui.text_size +define -2 gui.choice_button_text_xalign = 0.5 +define -2 gui.choice_button_text_idle_color = "#000" +define -2 gui.choice_button_text_hover_color = "#fa9" + + + + + + + + + +define -2 gui.slot_button_width = 276 +define -2 gui.slot_button_height = 206 +define -2 gui.slot_button_borders = Borders(10, 10, 10, 10) +define -2 gui.slot_button_text_size = 14 +define -2 gui.slot_button_text_xalign = 0.5 +define -2 gui.slot_button_text_idle_color = gui.idle_small_color +define -2 gui.slot_button_text_hover_color = gui.hover_color + + +define -2 config.thumbnail_width = 256 +define -2 config.thumbnail_height = 144 + + +define -2 gui.file_slot_cols = 3 +define -2 gui.file_slot_rows = 2 + + + + + + + + + +define -2 gui.navigation_xpos = 80 + + +define -2 gui.skip_ypos = 10 + + +define -2 gui.notify_ypos = 45 + + +define -2 gui.choice_spacing = 22 + + +define -2 gui.navigation_spacing = 6 + + +define -2 gui.pref_spacing = 10 + + +define -2 gui.pref_button_spacing = 0 + + +define -2 gui.page_spacing = 0 + + +define -2 gui.slot_spacing = 10 + + + + + + + + +define -2 gui.frame_borders = Borders(4, 4, 4, 4) + + +define -2 gui.confirm_frame_borders = Borders(40, 40, 40, 40) + + +define -2 gui.skip_frame_borders = Borders(16, 5, 50, 5) + + +define -2 gui.notify_frame_borders = Borders(16, 5, 40, 5) + + +define -2 gui.frame_tile = False + + + + + + + + + + + +define -2 gui.bar_size = 36 +define -2 gui.scrollbar_size = 12 +define -2 gui.slider_size = 30 + + +define -2 gui.bar_tile = False +define -2 gui.scrollbar_tile = False +define -2 gui.slider_tile = False + + +define -2 gui.bar_borders = Borders(4, 4, 4, 4) +define -2 gui.scrollbar_borders = Borders(4, 4, 4, 4) +define -2 gui.slider_borders = Borders(4, 4, 4, 4) + + +define -2 gui.vbar_borders = Borders(4, 4, 4, 4) +define -2 gui.vscrollbar_borders = Borders(4, 4, 4, 4) +define -2 gui.vslider_borders = Borders(4, 4, 4, 4) + + + +define -2 gui.unscrollable = "hide" + + + + + + + +define -2 config.history_length = 50 + + + +define -2 gui.history_height = None + + + +define -2 gui.history_name_xpos = 150 +define -2 gui.history_name_ypos = 0 +define -2 gui.history_name_width = 150 +define -2 gui.history_name_xalign = 1.0 + + +define -2 gui.history_text_xpos = 170 +define -2 gui.history_text_ypos = 5 +define -2 gui.history_text_width = 740 +define -2 gui.history_text_xalign = 0.0 + + + + + + + +define -2 gui.nvl_borders = Borders(0, 10, 0, 20) + + + +define -2 gui.nvl_height = 115 + + + +define -2 gui.nvl_spacing = 10 + + + +define -2 gui.nvl_name_xpos = 430 +define -2 gui.nvl_name_ypos = 0 +define -2 gui.nvl_name_width = 150 +define -2 gui.nvl_name_xalign = 1.0 + + +define -2 gui.nvl_text_xpos = 450 +define -2 gui.nvl_text_ypos = 8 +define -2 gui.nvl_text_width = 590 +define -2 gui.nvl_text_xalign = 0.0 + + + +define -2 gui.nvl_thought_xpos = 240 +define -2 gui.nvl_thought_ypos = 0 +define -2 gui.nvl_thought_width = 780 +define -2 gui.nvl_thought_xalign = 0.0 + + +define -2 gui.nvl_button_xpos = 450 +define -2 gui.nvl_button_xalign = 0.0 + + + + + + + +init -2 python: + + + + if renpy.variant("touch"): + + gui.quick_button_borders = Borders(60, 14, 60, 0) + + + + if renpy.variant("small"): + + + gui.text_size = 30 + gui.name_text_size = 36 + gui.notify_text_size = 25 + gui.interface_text_size = 36 + gui.button_text_size = 34 + gui.label_text_size = 36 + + + gui.textbox_height = 240 + gui.name_xpos = 80 + gui.text_xpos = 90 + gui.text_width = 1100 + + + gui.choice_button_width = 1240 + + gui.navigation_spacing = 20 + gui.pref_button_spacing = 10 + + gui.history_height = 190 + gui.history_text_width = 690 + + + gui.file_slot_cols = 2 + gui.file_slot_rows = 2 + + + gui.nvl_height = 170 + + gui.nvl_name_width = 305 + gui.nvl_name_xpos = 325 + + gui.nvl_text_width = 915 + gui.nvl_text_xpos = 345 + gui.nvl_text_ypos = 5 + + gui.nvl_thought_width = 1240 + gui.nvl_thought_xpos = 20 + + gui.nvl_button_width = 1240 + gui.nvl_button_xpos = 20 + + + gui.quick_button_text_size = 20 +# Decompiled by unrpyc: https://github.com/CensoredUsername/unrpyc diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index fa5be203d8..626f8fb9d4 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -250,7 +250,7 @@ init: # Recomputes the position of the ball, handles bounces, and # draws the screen. def render(self, width, height, st, at): - + global lose_on_purpose # The Render object we'll be drawing into. r = renpy.Render(width, height) @@ -295,7 +295,6 @@ init: # Moves Monika's paddle. It wants to go to self.by, but # may be limited by it's speed limit. - global lose_on_purpose if lose_on_purpose and self.bx >= self.COURT_WIDTH * 0.75: if self.bx <= self.PADDLE_X_MONIKA: if self.ctargety > self.computery: @@ -324,6 +323,8 @@ init: global win_streak_counter global loss_streak_counter global instant_loss_streak_counter + global pong_angle_last_shot + global ball_paddle_bounces # Render the paddle image. We give it an 1280x720 area # to render into, knowing that images will render smaller. @@ -362,13 +363,11 @@ init: elif angle < -self.MAX_ANGLE: angle = -self.MAX_ANGLE; - global pong_angle_last_shot pong_angle_last_shot = angle; self.bdy = .5 * math.sin(angle) self.bdx = math.copysign(.5 * math.cos(angle), -self.bdx) - global ball_paddle_bounces ball_paddle_bounces += 1 # Changes where the computer aims after a hit. diff --git a/Monika After Story/game/splash.rpy b/Monika After Story/game/splash.rpy index 451957f5f5..58b06aa1fe 100644 --- a/Monika After Story/game/splash.rpy +++ b/Monika After Story/game/splash.rpy @@ -2,12 +2,12 @@ ## ## Before load, check to be sure that the archive files were found. ## If not, display an error message and quit. -init -100 python: - #Check for each archive needed - for archive in ['audio','images','scripts','fonts']: - if not archive in config.archives: - #If one is missing, throw an error and chlose - renpy.error("DDLC archive files not found in /game folder. Check installation and try again.") +# init -100 python: +# #Check for each archive needed +# for archive in ['audio','images','scripts','fonts']: +# if not archive in config.archives: +# #If one is missing, throw an error and chlose +# renpy.error("DDLC archive files not found in /game folder. Check installation and try again.") ## First, a disclaimer declaring this is a mod is shown, then there is a ## check for the original DDLC assets in the install folder. If those are diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 962e9e302b..416f32df6c 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -71,7 +71,6 @@ init -45 python: """ import hashlib # sha256 signatures import base64 # "packing" shipments involve base64 - from StringIO import StringIO as StringIO from io import StringIO import store.mas_utils as mas_utils # logging From 7d3252e72a16c91c6b0cb103ea11b07fb8d42fe3 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 10:57:40 -0500 Subject: [PATCH 059/180] why did we do this lol --- Monika After Story/game/script-holidays.rpy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/script-holidays.rpy b/Monika After Story/game/script-holidays.rpy index 2711d8e6ae..a99d658e5a 100644 --- a/Monika After Story/game/script-holidays.rpy +++ b/Monika After Story/game/script-holidays.rpy @@ -340,9 +340,9 @@ init 501 python: ) init python: - MAS_O31_COSTUME_CG_MAP = { - mas_clothes_marisa: "o31mcg", - mas_clothes_rin: "o31rcg" + MAS_O31_COSTUME_CG_MAP: dict[str, str] = { + mas_clothes_marisa.name: "o31mcg", + mas_clothes_rin.name: "o31rcg" } #Functions @@ -523,8 +523,8 @@ init -10 python: if wearing_costume: #Check if the current costume is in the cg map, and if so, prep the cg - if monika_chr.clothes in MAS_O31_COSTUME_CG_MAP: - store.mas_o31_event.cg_decoded = store.mas_o31_event.decodeImage(MAS_O31_COSTUME_CG_MAP[monika_chr.clothes]) + if monika_chr.clothes.name in MAS_O31_COSTUME_CG_MAP: + store.mas_o31_event.cg_decoded = store.mas_o31_event.decodeImage(MAS_O31_COSTUME_CG_MAP[monika_chr.clothes.name]) return monika_chr.clothes return None @@ -549,8 +549,8 @@ init -10 python: random_outfit = random.choice(selection_pool) #Setup the image decode - if random_outfit in MAS_O31_COSTUME_CG_MAP: - store.mas_o31_event.cg_decoded = store.mas_o31_event.decodeImage(MAS_O31_COSTUME_CG_MAP[random_outfit]) + if random_outfit.name in MAS_O31_COSTUME_CG_MAP: + store.mas_o31_event.cg_decoded = store.mas_o31_event.decodeImage(MAS_O31_COSTUME_CG_MAP[random_outfit.name]) #And return the outfit return random_outfit From db00c5cdefa07efd9cf8f089348e0a101090cf69 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 10:58:58 -0500 Subject: [PATCH 060/180] drop some dict iter funcs --- Monika After Story/game/sprite-decoder.rpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/sprite-decoder.rpy b/Monika After Story/game/sprite-decoder.rpy index 04a6240e7b..858ada6d96 100644 --- a/Monika After Story/game/sprite-decoder.rpy +++ b/Monika After Story/game/sprite-decoder.rpy @@ -56,8 +56,8 @@ init python in mas_sprite_decoder: SWEAT_MAP = jobj["sweat"] MOD_MAP = jobj["MOD_MAP"] # Convert lists into sets for speed - for sub_map in MOD_MAP.itervalues(): - for key, value in sub_map.iteritems(): + for sub_map in MOD_MAP.values(): + for key, value in sub_map.items(): sub_map[key] = set(value) #Since tuples aren't supported in json, we need to do some conversion here From facd2a476ca7936b22ecf7283a0ff5312de1a8a5 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 11:08:46 -0500 Subject: [PATCH 061/180] change a few StringIO -> BytesIO --- Monika After Story/game/zz_dockingstation.rpy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 416f32df6c..d1d3bbf68d 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -71,7 +71,7 @@ init -45 python: """ import hashlib # sha256 signatures import base64 # "packing" shipments involve base64 - from io import StringIO + from io import BytesIO, StringIO import store.mas_utils as mas_utils # logging @@ -344,7 +344,7 @@ init -45 python: """ box = None try: - box = self.StringIO() + box = self.BytesIO() return (box, self._pack(contents, box, True, pkg_slip)) @@ -590,7 +590,7 @@ init -45 python: # internalize contents so we can do proper file closing if contents is None: - _contents = self.StringIO() + _contents = self.BytesIO() else: _contents = contents @@ -698,7 +698,7 @@ init -45 python: def unpackPackage(self, package, pkg_slip=None): """ Unpacks a package - (decodes a base64 file into a regular StringIO buffer) + (decodes a base64 file into a regular BytesIO buffer) NOTE: may throw exceptions @@ -711,7 +711,7 @@ init -45 python: (Default: None) RETURNS: - StringIO buffer containing the package decoded + BytesIO buffer containing the package decoded Or None if pkg_slip checksum was passed in and the given package failed the checksum """ @@ -721,7 +721,7 @@ init -45 python: contents = None try: # NOTE: we use regular StringIO in case of unicode - contents = self.StringIO() + contents = self.BytesIO() _pkg_slip = self._unpack( package, From 5dd6d9e4850b487c901a48fa93c9c529df6ada8d Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 12:04:50 -0500 Subject: [PATCH 062/180] no more reliance on base DDLC poemgame --- .../mod_assets/games/hangman/poemwords.txt | 235 ++++++++++++++++++ Monika After Story/game/zz_hangman.rpy | 27 +- Monika After Story/game/zz_poemgame.rpy | 38 +-- 3 files changed, 276 insertions(+), 24 deletions(-) create mode 100644 Monika After Story/game/mod_assets/games/hangman/poemwords.txt diff --git a/Monika After Story/game/mod_assets/games/hangman/poemwords.txt b/Monika After Story/game/mod_assets/games/hangman/poemwords.txt new file mode 100644 index 0000000000..bdf438386b --- /dev/null +++ b/Monika After Story/game/mod_assets/games/hangman/poemwords.txt @@ -0,0 +1,235 @@ +#File format: word,sPoint,nPoint,yPoint + +#Sayori's winning words +happiness,3,2,1 +sadness,3,2,1 +death,3,1,2 +tragedy,3,1,2 +alone,3,1,2 +love,3,2,1 +adventure,3,2,1 +sweet,3,2,1 +excitement,3,2,1 +fireworks,3,2,1 +romance,3,2,1 +tears,3,1,2 +depression,3,1,2 +heart,3,2,1 +marriage,3,2,1 +passion,3,2,1 +childhood,3,2,1 +fun,3,2,1 +color,3,2,1 +hope,3,1,2 +friends,3,2,1 +family,3,2,1 +party,3,2,1 +vacation,3,2,1 +lazy,3,2,1 +daydream,3,1,2 +pain,3,1,2 +holiday,3,2,1 +bed,3,2,1 +feather,3,2,1 +shame,3,1,2 +fear,3,1,2 +warm,3,2,1 +flower,3,2,1 +comfort,3,2,1 +dance,3,2,1 +sing,3,2,1 +cry,3,1,2 +laugh,3,2,1 +dark,3,1,2 +sunny,3,2,1 +raincloud,3,2,1 +calm,3,1,2 +silly,3,2,1 +flying,3,2,1 +wonderful,3,2,1 +unrequited,3,1,2 +rose,3,1,2 +together,3,2,1 +promise,3,2,1 +charm,3,2,1 +beauty,3,2,1 +cheer,3,2,1 +smile,3,2,1 +broken,3,1,2 +precious,3,2,1 +prayer,3,1,2 +clumsy,3,2,1 +forgive,3,1,2 +nature,3,2,1 +ocean,3,2,1 +dazzle,3,2,1 +special,3,2,1 +music,3,2,1 +lucky,3,2,1 +misfortune,3,1,2 +loud,3,2,1 +peaceful,3,1,2 +joy,3,1,2 +sunset,3,2,1 +fireflies,3,2,1 +rainbow,3,2,1 +hurt,3,1,2 +play,3,2,1 +sparkle,3,2,1 +scars,3,1,2 +empty,3,1,2 +amazing,3,2,1 +grief,3,1,2 +embrace,3,1,2 +extraordinary,3,2,1 +awesome,3,2,1 +defeat,3,1,2 +hopeless,3,1,2 +misery,3,1,2 +treasure,3,2,1 +bliss,3,2,1 +memories,3,2,1 + +#Natsuki's words +cute,2,3,1 +fluffy,2,3,1 +pure,1,3,2 +candy,2,3,1 +shopping,2,3,1 +puppy,2,3,1 +kitty,2,3,1 +clouds,2,3,1 +lipstick,1,3,2 +parfait,2,3,1 +strawberry,2,3,1 +pink,2,3,1 +chocolate,2,3,1 +heartbeat,1,3,2 +kiss,1,3,2 +melody,2,3,1 +ribbon,2,3,1 +jumpy,2,3,1 +doki-doki,2,3,1 +kawaii,2,3,1 +skirt,2,3,1 +cheeks,2,3,1 +email,2,3,1 +sticky,2,3,1 +bouncy,2,3,1 +shiny,2,3,1 +nibble,2,3,1 +fantasy,1,3,2 +sugar,2,3,1 +giggle,2,3,1 +marshmallow,2,3,1 +hop,2,3,1 +skipping,2,3,1 +peace,2,3,1 +spinning,2,3,1 +twirl,2,3,1 +lollipop,2,3,1 +poof,2,3,1 +bubbles,2,3,1 +whisper,2,3,1 +summer,2,3,1 +waterfall,1,3,2 +swimsuit,2,3,1 +vanilla,2,3,1 +headphones,2,3,1 +games,2,3,1 +socks,2,3,1 +hair,2,3,1 +playground,2,3,1 +nightgown,1,3,2 +blanket,1,3,2 +milk,2,3,1 +pout,2,3,1 +anger,2,3,1 +papa,2,3,1 +valentine,2,3,1 +mouse,1,3,2 +whistle,2,3,1 +boop,2,3,1 +bunny,2,3,1 +anime,2,3,1 +jump,2,3,1 + +#Yuri's words +determination,1,1,3 +suicide,2,1,3 +imagination,2,1,3 +secretive,2,1,3 +vitality,1,1,3 +existence,2,1,3 +effulgent,1,1,3 +crimson,1,1,3 +whirlwind,1,1,3 +afterimage,1,1,3 +vertigo,1,1,3 +disoriented,1,1,3 +essence,2,1,3 +ambient,2,1,3 +starscape,2,1,3 +disarray,1,1,3 +contamination,1,1,3 +intellectual,1,1,3 +analysis,1,1,3 +entropy,1,1,3 +vivacious,1,1,3 +uncanny,2,1,3 +incongruent,1,1,3 +wrath,2,1,3 +heavensent,2,1,3 +massacre,2,1,3 +philosophy,1,1,3 +fickle,1,1,3 +tenacious,1,1,3 +aura,2,1,3 +unstable,1,1,3 +inferno,2,1,3 +incapable,2,1,3 +destiny,2,1,3 +infallible,1,1,3 +agonizing,2,1,3 +variance,1,1,3 +uncontrollable,2,1,3 +extreme,1,1,3 +flee,2,1,3 +dream,2,2,3 +disaster,2,1,3 +vivid,2,1,3 +vibrant,1,2,3 +question,1,2,3 +fester,2,1,3 +judgment,1,1,3 +cage,1,2,3 +explode,1,2,3 +pleasure,1,2,3 +lust,1,2,3 +sensation,1,2,3 +climax,1,2,3 +electricity,1,2,3 +disown,1,1,3 +despise,2,1,3 +infinite,2,1,3 +eternity,2,1,3 +time,2,1,3 +universe,2,1,3 +unending,2,1,3 +raindrops,2,1,3 +covet,1,1,3 +unrestrained,1,1,3 +landscape,2,1,3 +portrait,2,1,3 +journey,2,1,3 +meager,1,1,3 +anxiety,2,1,3 +frightening,2,1,3 +horror,2,1,3 +melancholy,2,1,3 +insight,2,1,3 +atone,2,1,3 +breathe,1,2,3 +captive,2,1,3 +desire,1,2,3 +graveyard,2,1,3 \ No newline at end of file diff --git a/Monika After Story/game/zz_hangman.rpy b/Monika After Story/game/zz_hangman.rpy index ce919dc6fe..153cda5653 100644 --- a/Monika After Story/game/zz_hangman.rpy +++ b/Monika After Story/game/zz_hangman.rpy @@ -188,10 +188,11 @@ init -1 python in mas_hangman: def _add_monika_words(wordlist): for word in MONI_WORDS: - wordlist.append(renpy.store.PoemWord(glitch=False,sPoint=0,yPoint=0,nPoint=0,word=word)) + wordlist.append(renpy.store.MASPoemWord(sPoint=0,yPoint=0,nPoint=0, mPoint=4, word=word)) # file names + EASY_LIST = "mod_assets/games/hangman/poemwords.txt" NORMAL_LIST = "mod_assets/games/hangman/MASpoemwords.txt" HARD_LIST = "mod_assets/games/hangman/1000poemwords.txt" @@ -244,10 +245,25 @@ init -1 python in mas_hangman: easy_list = all_hm_words[EASY_MODE] # lets start with Non Monika words - easy_list[:] = [ - store.MASPoemWord._build(word, 0)._hangman() - for word in store.full_wordlist - ] + with open(renpy.config.gamedir + "/" + EASY_LIST, "r") as poemwords: + for line in poemwords: + line = line.strip() + + #Ignore line if commented/empty + if line == '' or line[0] == '#': + continue + + # add the word + splitword = line.split(',') + print(splitword) + easy_list.append(store.MASPoemWord( + splitword[0], + float(splitword[1]), + float(splitword[2]), + float(splitword[3]), + 0 + )) + # now for monika words moni_list = list() @@ -257,7 +273,6 @@ init -1 python in mas_hangman: copyWordsList(EASY_MODE) - def buildNormalList(): """ Builds the normal word list diff --git a/Monika After Story/game/zz_poemgame.rpy b/Monika After Story/game/zz_poemgame.rpy index 8814164e25..9fb691ddcd 100644 --- a/Monika After Story/game/zz_poemgame.rpy +++ b/Monika After Story/game/zz_poemgame.rpy @@ -142,24 +142,26 @@ init -4 python: def readInFile(self, wordfile): - # copied from poemgame (with adjustments) - # - # Reads in a file into the wordlist - # - # NOTE: On the poemwords file - # The file must consist of the following format: - # word,#1,#2,#3,#4 - # WHERE: - # word - the word we want in the poem - # #1 - the points this word gives to sayori - # #2 - the points this word gives to natsuki - # #3 - the points this word gives to yuri - # #4 - the points this word gives to monika - # (LINES that strat with # are ignored) - # - # IN: - # wordfile - the filename/path of the file to read words - with renpy.file(wordfile) as words: + """ + copied from poemgame (with adjustments) + + Reads in a file into the wordlist + + NOTE: On the poemwords file + The file must consist of the following format: + word,#1,#2,#3,#4 + WHERE: + word - the word we want in the poem + #1 - the points this word gives to sayori + #2 - the points this word gives to natsuki + #3 - the points this word gives to yuri + #4 - the points this word gives to monika + (LINES that strat with # are ignored) + + IN: + wordfile - the filename/path of the file to read words + """ + with open(f"{renpy.config.gamedir}/{wordfile}", "r") as words: for line in words: line = line.strip() From 1c39a879808f358b32a6ba7e4b4821064ffa4e68 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 13:55:03 -0500 Subject: [PATCH 063/180] fix spritegen + docs, get more transforms to MAS --- Monika After Story/game/definitions.rpy | 3 +- Monika After Story/game/script-ch30.rpy | 2 +- Monika After Story/game/sprite-generator.rpy | 2 +- Monika After Story/game/styles.rpy | 11 +++ Monika After Story/game/zz_dockingstation.rpy | 4 +- Monika After Story/game/zz_transforms.rpy | 70 +++++++++++++++++++ 6 files changed, 87 insertions(+), 5 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 5f16fb49b0..06d22520d9 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -32,6 +32,7 @@ python early: import io import datetime import traceback + import string # define the zorders MAS_MONIKA_Z = 10 @@ -172,7 +173,7 @@ python early: # otherwise just get the reference else: - first, rest = field_name._formatter_field_name_split() + first, rest = string._string.formatter_field_name_split(field_name) obj = self.get_value(first, args, kwargs) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 593e5b8bc8..b4c46d881a 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -39,7 +39,7 @@ init -890 python in mas_globals: store.persistent._mas_pm_has_went_back_in_time = True #Internal renpy version check - is_r7 = renpy.version(True)[0] == 7 + is_at_least_r7 = renpy.version(True)[0] >= 7 # Check whether or not the user uses a steam install is_steam = "steamapps" in renpy.config.basedir.lower() diff --git a/Monika After Story/game/sprite-generator.rpy b/Monika After Story/game/sprite-generator.rpy index a08da1643a..d9e9b5ddc8 100644 --- a/Monika After Story/game/sprite-generator.rpy +++ b/Monika After Story/game/sprite-generator.rpy @@ -12,7 +12,7 @@ init python in mas_sprites: name - tuple of strings (tag, attributes) d - displayables """ - if store.mas_globals.is_r7: + if store.mas_globals.is_at_least_r7: renpy.display.image.register_image(name, d) else: diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index 93fc933cab..46c7210639 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -657,3 +657,14 @@ init 25 python in mas_ui: scr.scope["flt_evs"] = _twopane_menu_search_events(search_query) # Update the screen renpy.restart_interaction() + +##BASE DDLC Console Styles +style console_text: + font "gui/font/F25_Bank_Printer.ttf" + color "#fff" + size 18 + outlines [] + + +style console_text_console is console_text: + slow_cps 30 diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index d1d3bbf68d..61adead460 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -339,7 +339,7 @@ init -45 python: RETURNS: tuple of the following format: - [0] - base64 version of the given data, in a cStringIO buffer + [0] - base64 version of the given data, in a BytesIO buffer [1] - sha256 checksum if pkg_slip is True, None otherwise """ box = None @@ -1223,7 +1223,7 @@ init 200 python in mas_dockstat: import store.mas_greetings as mas_greetings import store.mas_ics as mas_ics import store.evhand as evhand - from cStringIO import StringIO as StringIO + from io import StringIO import codecs import re import os diff --git a/Monika After Story/game/zz_transforms.rpy b/Monika After Story/game/zz_transforms.rpy index 864d03f1a3..8df9ee3add 100644 --- a/Monika After Story/game/zz_transforms.rpy +++ b/Monika After Story/game/zz_transforms.rpy @@ -213,3 +213,73 @@ transform streaming_tears_transform(open_eyes_img, closed_eyes_img): closed_eyes_img 0.15 repeat + +transform tcommon(x=640, z=0.80): + yanchor 1.0 subpixel True + on show: + ypos 1.03 + zoom z*0.95 alpha 0.00 + xcenter x yoffset -20 + easein .25 yoffset 0 zoom z*1.00 alpha 1.00 + on replace: + + alpha 1.00 + parallel: + easein .25 xcenter x zoom z*1.00 + parallel: + easein .15 yoffset 0 ypos 1.03 + +transform tinstant(x=640, z=0.80): + xcenter x yoffset 0 zoom z*1.00 alpha 1.00 yanchor 1.0 ypos 1.03 + +transform t41: + tcommon(200) +transform t42: + tcommon(493) +transform t43: + tcommon(786) +transform t44: + tcommon(1080) +transform t31: + tcommon(240) +transform t32: + tcommon(640) +transform t33: + tcommon(1040) +transform t21: + tcommon(400) +transform t22: + tcommon(880) +transform t11: + tcommon(640) + +transform i41: + tinstant(200) +transform i42: + tinstant(493) +transform i43: + tinstant(786) +transform i44: + tinstant(1080) +transform i31: + tinstant(240) +transform i32: + tinstant(640) +transform i33: + tinstant(1040) +transform i21: + tinstant(400) +transform i22: + tinstant(880) +transform i11: + tinstant(640) + +transform sticker_hop: + easein_quad .18 yoffset -80 + easeout_quad .18 yoffset 0 + easein_quad .18 yoffset -80 + easeout_quad .18 yoffset 0 + +transform sticker_move_n: + easein_quad .08 yoffset -15 + easeout_quad .08 yoffset 0 From f873487621cae47e2bdd89271c50998f2e6cf737 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 14:13:20 -0500 Subject: [PATCH 064/180] no more iter funcs --- .../game/dev/dev_exp_previewer.rpy | 10 +++++----- Monika After Story/game/dev/dev_islands.rpy | 2 +- Monika After Story/game/script-compliments.rpy | 2 +- .../game/script-islands-event.rpy | 18 +++++++++--------- Monika After Story/game/zz_backgrounds.rpy | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Monika After Story/game/dev/dev_exp_previewer.rpy b/Monika After Story/game/dev/dev_exp_previewer.rpy index 6aec0eef58..9066eba99c 100644 --- a/Monika After Story/game/dev/dev_exp_previewer.rpy +++ b/Monika After Story/game/dev/dev_exp_previewer.rpy @@ -154,14 +154,14 @@ init 999 python: REVERSE_IMG_NAMES_MAP = { key: { v: k - for k, v in sub_map.iteritems() + for k, v in sub_map.items() } - for key, sub_map in IMG_NAMES_MAP.iteritems() + for key, sub_map in IMG_NAMES_MAP.items() } # And also handle the keys from the mod map # (different keys that actually correspond to the same sprite letters) - for key, sub_map in MOD_MAP.iteritems(): - for k, v in sub_map.iteritems(): + for key, sub_map in MOD_MAP.items(): + for k, v in sub_map.items(): spr_code_letter = REVERSE_IMG_NAMES_MAP[key][k] for mod_str in v: REVERSE_IMG_NAMES_MAP[key][k + mod_str] = spr_code_letter @@ -1051,7 +1051,7 @@ init 999 python: # Add bits that might not be present in the given code exp_kwargs.setdefault(key, None) - for key, value in exp_kwargs.iteritems(): + for key, value in exp_kwargs.items(): # Skip the keys we don't need if ( key in self.SPRITE_CODE_MAP diff --git a/Monika After Story/game/dev/dev_islands.rpy b/Monika After Story/game/dev/dev_islands.rpy index 9e36a5fd7b..5fc5fc3070 100644 --- a/Monika After Story/game/dev/dev_islands.rpy +++ b/Monika After Story/game/dev/dev_islands.rpy @@ -69,7 +69,7 @@ label dev_test_islands_progress: k, ", ".join(map(str, v)) ) - for k, v in data.iteritems() + for k, v in data.items() ] ) diff --git a/Monika After Story/game/script-compliments.rpy b/Monika After Story/game/script-compliments.rpy index 354a3a92dd..d5cf8f66e5 100644 --- a/Monika After Story/game/script-compliments.rpy +++ b/Monika After Story/game/script-compliments.rpy @@ -68,7 +68,7 @@ label monika_compliments: # build menu list compliments_menu_items = [ (ev.prompt, ev_label, not seen_event(ev_label), False) - for ev_label, ev in mas_compliments.compliment_database.iteritems() + for ev_label, ev in mas_compliments.compliment_database.items() if ( Event._filterEvent(ev, unlocked=True, aff=mas_curr_affection, flag_ban=EV_FLAG_HFM) and ev.checkConditional() diff --git a/Monika After Story/game/script-islands-event.rpy b/Monika After Story/game/script-islands-event.rpy index 7f4391a2e2..46f0634f5c 100644 --- a/Monika After Story/game/script-islands-event.rpy +++ b/Monika After Story/game/script-islands-event.rpy @@ -232,7 +232,7 @@ init -20 python in mas_island_event: # FIXME: py3 update return { id_: data.default_unlocked - for id_, data in cls._data_map.iteritems() + for id_, data in cls._data_map.items() } @classmethod @@ -246,7 +246,7 @@ init -20 python in mas_island_event: # FIXME: py3 update return { id_: data.fp_map - for id_, data in cls._data_map.iteritems() + for id_, data in cls._data_map.items() if data.type == type_ and data.fp_map } @@ -714,8 +714,8 @@ init -25 python in mas_island_event: map_ - the map to get filenames from, and which will be overriden """ # FIXME: py3 update - for name, path_map in map_.iteritems(): - for sprite_type, path in path_map.iteritems(): + for name, path_map in map_.items(): + for sprite_type, path in path_map.items(): raw_data = zip_file.read(path) img = store.MASImageData(raw_data, "{}_{}.png".format(name, sprite_type)) path_map[sprite_type] = img @@ -761,7 +761,7 @@ init -25 python in mas_island_event: global island_disp_map, decal_disp_map, obj_disp_map, bg_disp_map, overlay_disp_map # Build the islands - for island_name, img_map in island_imgs_maps.iteritems(): + for island_name, img_map in island_imgs_maps.items(): disp = IslandFilterWeatherDisplayable( day=MASWeatherMap( { @@ -792,7 +792,7 @@ init -25 python in mas_island_event: island_disp_map[island_name] = partial_disp(disp) # Build the decals - for decal_name, img_map in decal_imgs_maps.iteritems(): + for decal_name, img_map in decal_imgs_maps.items(): disp = IslandFilterWeatherDisplayable( day=MASWeatherMap( { @@ -823,7 +823,7 @@ init -25 python in mas_island_event: decal_disp_map[decal_name] = partial_disp(disp) # Build the bg - for bg_name, img_map in bg_imgs_maps.iteritems(): + for bg_name, img_map in bg_imgs_maps.items(): disp = IslandFilterWeatherDisplayable( day=MASWeatherMap( { @@ -858,7 +858,7 @@ init -25 python in mas_island_event: "overlay_rain": 0.8, "overlay_snow": 3.5 } - for overlay_name, img_map in overlay_imgs_maps.iteritems(): + for overlay_name, img_map in overlay_imgs_maps.items(): # Overlays are just dynamic displayables partial_disp = IslandsImageDefinition.getDataFor(overlay_name).partial_disp overlay_disp_map[overlay_name] = store.mas_islands_weather_overlay_transform( @@ -1171,7 +1171,7 @@ init -25 python in mas_island_event: sub_displayables = list() # Add all unlocked islands - for key, disp in island_disp_map.iteritems(): + for key, disp in island_disp_map.items(): if _isUnlocked(key): _reset_parallax_disp(disp) sub_displayables.append(disp) diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index 0a9d96fbfc..d5a9c84bb6 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -3227,7 +3227,7 @@ label monika_change_background_loop: if persistent._mas_o31_in_o31_mode: other_backgrounds = [ (mbg_obj.prompt, mbg_obj, False, False) - for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.iteritems() + for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.items() if mbg_id != "spaceroom" and mbg_obj.unlocked and mas_doesBackgroundHaveHolidayDeco(MAS_O31_DECO_TAGS, mbg_id) ] @@ -3235,7 +3235,7 @@ label monika_change_background_loop: elif persistent._mas_d25_deco_active: other_backgrounds = [ (mbg_obj.prompt, mbg_obj, False, False) - for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.iteritems() + for mbg_id, mbg_obj in mas_background.BACKGROUND_MAP.items() if mbg_id != "spaceroom" and mbg_obj.unlocked and mas_doesBackgroundHaveHolidayDeco(mas_d25_utils.DECO_TAGS, mbg_id) ] From abc2e551cbb7a51ced9e79cddf8e1c3dd834199a Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 14:25:53 -0500 Subject: [PATCH 065/180] use new lib name for HTTP stuffs --- Monika After Story/game/updater.rpy | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Monika After Story/game/updater.rpy b/Monika After Story/game/updater.rpy index e86198d13c..4c1536c4fd 100644 --- a/Monika After Story/game/updater.rpy +++ b/Monika After Story/game/updater.rpy @@ -329,14 +329,12 @@ init -1 python: new_url - the redirect we want to connect to Returns read_json if we got a connection, Nnone otherwise """ - import httplib + from http.client import HTTPConnection, HTTPException _http, double_slash, url = new_url.partition("//") url, single_slash, req_uri = url.partition("/") read_json = None - h_conn = httplib.HTTPConnection( - url - ) + h_conn = HTTPConnection(url) try: # make connection @@ -352,7 +350,7 @@ init -1 python: read_json = server_response.read() - except httplib.HTTPException: + except HTTPException: # we assume a timeout / connection error return None @@ -382,7 +380,7 @@ init -1 python: _thread_result appends appropriate state for use """ - import httplib + from http.client import HTTPConnection, HTTPException import json # separate the update link parts @@ -390,9 +388,7 @@ init -1 python: _http, double_slash, url = update_link.partition("//") url, single_slash, json_file = url.partition("/") read_json = None - h_conn = httplib.HTTPConnection( - url - ) + h_conn = HTTPConnection(url) try: # make connection and attempt to connect @@ -430,7 +426,7 @@ init -1 python: # good status, lets get the value read_json = server_response.read() - except httplib.HTTPException: + except HTTPException: # we assume a timeout / connection error thread_result.append(MASUpdaterDisplayable.STATE_TIMEOUT) return From 780b23c1af9c651b4cb6405d4310c2816b1ea25a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 22 Feb 2022 00:39:11 +0300 Subject: [PATCH 066/180] Update tools for py3 --- tools/gamedir.py | 2 +- tools/menutils.py | 18 ++++++++--------- tools/renpy_lint_parser.py | 41 +++++++++++++++++++------------------- tools/sprite.py | 2 +- tools/spritemaker.py | 9 ++++----- tools/spritepuller.py | 8 ++++---- tools/testsgenerator.py | 1 - 7 files changed, 39 insertions(+), 42 deletions(-) diff --git a/tools/gamedir.py b/tools/gamedir.py index e44479845b..0502b6ce8c 100644 --- a/tools/gamedir.py +++ b/tools/gamedir.py @@ -1,5 +1,5 @@ # module containing constants about game directory -# +# # VER: py27 REL_PATH_GAME = "../Monika After Story/game/" diff --git a/tools/menutils.py b/tools/menutils.py index 6e434d8186..edc54de582 100644 --- a/tools/menutils.py +++ b/tools/menutils.py @@ -76,7 +76,7 @@ def menu(menu_opts, defindex=None): defval = None footer = MENU_END.format("[0]") - except: + except Exception: # if we failed, None everything so we dont do foolish things later defindex = None defval = None @@ -111,7 +111,7 @@ def menu(menu_opts, defindex=None): print(footer + "\n") # and then prompt! - user_input = raw_input(prompt) + user_input = input(prompt) # NOTE: if blank, we just return the default value if len(user_input) <= 0: @@ -125,11 +125,11 @@ def menu(menu_opts, defindex=None): # and user input is valid, return the result return menu_opts[user_input][1] - elif user_input == 0: + if user_input == 0: # user wants to go back return None - except: + except Exception: # bad user input pass @@ -194,7 +194,7 @@ def restrict(page_value): print(PAGE_ENTRY.format(str_func(item))) # action string and user input - user_input = raw_input(PAGE_BAR.join(action_bar)).lower() + user_input = input(PAGE_BAR.join(action_bar)).lower() # process user input if user_input == __QUIT: @@ -209,11 +209,11 @@ def restrict(page_value): page += 1 elif user_input == __GOTO: - page_input = raw_input(PAGE_GOTO_PROMPT.format(last_page+1)) + page_input = input(PAGE_GOTO_PROMPT.format(last_page+1)) try: page = int(page_input)-1 - except: + except Exception: # bad page input pass @@ -246,7 +246,7 @@ def ask(question, def_no=True): yes = "Y" no = "n" - choice = raw_input("{0}? ({1}/{2}): ".format(question, yes, no)).lower() + choice = input("{0}? ({1}/{2}): ".format(question, yes, no)).lower() # check default if len(choice) <= 0: @@ -270,7 +270,7 @@ def e_pause(): """ Generic enter to continue """ - abc = raw_input("\n\n (Press Enter to continue)") + abc = input("\n\n (Press Enter to continue)") def header(title): diff --git a/tools/renpy_lint_parser.py b/tools/renpy_lint_parser.py index 4d235ec30a..c826bd2132 100644 --- a/tools/renpy_lint_parser.py +++ b/tools/renpy_lint_parser.py @@ -1,14 +1,15 @@ -# parses renpy_output and removes missing dynamic image lines. +# parses renpy_output and removes missing dynamic image lines. # (and other known things) import re -import os +# import os +import sys # regex parsing IMG_NOT_FOUND = re.compile( - "\w+/(\w+/)*.+\.rpy:\d+ (Could not find image \(monika |The image named 'monika )(\d\w\w\w+|\d\w|1|5|4|g1|g2)" + r"\w+/(\w+/)*.+\.rpy:\d+ (Could not find image \(monika |The image named 'monika )(\d\w\w\w+|\d\w|1|5|4|g1|g2)" ) # file load @@ -17,21 +18,19 @@ OUT_FILENAME = "renpy_output_clean" # load files -INFILE = open(IN_FILENAME, "r") -OUTFILE = open(OUT_FILENAME, "w") - -if not INFILE or not OUTFILE: - print("file load failed") - exit(1) - -# loop and clean -for line in INFILE: - if ( - len(line.strip()) > 0 - and not IMG_NOT_FOUND.match(line) - ): - OUTFILE.write(line) - -INFILE.close() -OUTFILE.close() -exit(0) +try: + with open(IN_FILENAME, "r") as infile, open(OUT_FILENAME, "w") as outfile: + # loop and clean + for line in infile: + if ( + len(line.strip()) > 0 + and not IMG_NOT_FOUND.match(line) + ): + outfile.write(line) + +except Exception as e: + print(f"File load failed: {e}") + sys.exit(1) + +else: + sys.exit(0) diff --git a/tools/sprite.py b/tools/sprite.py index 27051bd4be..a70b97bfbc 100644 --- a/tools/sprite.py +++ b/tools/sprite.py @@ -7,7 +7,7 @@ draw_function = DRAW_MONIKA_IM -class StaticSprite(object): +class StaticSprite(): """ A static sprite is a sprite that knows its sprite codes and more diff --git a/tools/spritemaker.py b/tools/spritemaker.py index 5219df559b..6d64e0ee5b 100644 --- a/tools/spritemaker.py +++ b/tools/spritemaker.py @@ -1,8 +1,6 @@ # makes sprites # can also load sprites -from __future__ import print_function - import os import gamedir as GDIR import menutils @@ -19,7 +17,7 @@ # classes -class SortedKeySpriteDBIter(object): +class SortedKeySpriteDBIter(): """ Iterator over a sprite db. This iterates so that the StaticSprites are in key order (aka from the given list of keys) @@ -39,6 +37,7 @@ def __init__(self, sprite_db, sprite_db_keys): self.__default_fs.invalid = True def __iter__(self): + # This is wrong, but somehow works? return self def next(self): @@ -140,7 +139,7 @@ def filter(self, otherStaticSprite): if otherStaticSprite.invalid: return False - for flt in self.__filter_eq_map.itervalues(): + for flt in self.__filter_eq_map.values(): if not flt(otherStaticSprite): return False @@ -736,7 +735,7 @@ def make_sprite_bc(sprite_db, sprite_db_keys): while not_valid_code: menutils.clear_screen() print("\n\n") - trycode = raw_input("Enter a sprite code: ") + trycode = input("Enter a sprite code: ") # build a static sprite with the code new_sprite = StaticSprite(trycode) diff --git a/tools/spritepuller.py b/tools/spritepuller.py index 6f85fd392a..01885be7df 100644 --- a/tools/spritepuller.py +++ b/tools/spritepuller.py @@ -63,7 +63,7 @@ def is_sprite_line(line): Checks if the given line is a sprite line NOTE: a sprite line is a line that starts with "image monika" - + NOTE: does not strip the given line. IN: @@ -131,7 +131,7 @@ def pull_sprite_list(as_dict=False): if as_dict: # do we want a dict instead? sprite_dict = dict() - + for sprite in sprite_list: sprite_dict[sprite] = 0 @@ -180,7 +180,7 @@ def write_spritecodes(sprites): def write_spritestats(sprites): """ - Writes out a sprite file that just contains each sprite code with its + Writes out a sprite file that just contains each sprite code with its value, one sprite code per line IN: @@ -222,7 +222,7 @@ def write_zz_sprite_opt(sprites): # 1 last footer needed if open_list: outfile.write(__ZZ_SP_OPT_LINE_END) - + outfile.write(__ZZ_SP_OPT_FOOTER) diff --git a/tools/testsgenerator.py b/tools/testsgenerator.py index 4295631b70..c023a229f7 100644 --- a/tools/testsgenerator.py +++ b/tools/testsgenerator.py @@ -89,4 +89,3 @@ def run(): """ __ZZ_EXPR_TEST_LINE = ' m {0} "{0}"' __ZZ_EXPR_TEST_FOOTER = " return" - From b66761258ac38ae385ad29c375c9d525825c526a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 22 Feb 2022 00:42:15 +0300 Subject: [PATCH 067/180] use py3.9.6 for CI --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 8f51cc0f5b..1d83152468 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -33,7 +33,7 @@ jobs: uses: actions/setup-python@v2.2.1 with: # Version range or exact version of a Python version to use, using SemVer's version range syntax. - python-version: 2.7.18 # optional, default is 3.x + python-version: 3.9.6 # optional, default is 3.x # dl renpy src - name: Download rpy source From 035c0b66c7a84a842d556b1ba7ba78ebe543c1a3 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 22 Feb 2022 00:44:05 +0300 Subject: [PATCH 068/180] use the latest `setup-python` --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 1d83152468..000fed9110 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2.2.1 + uses: actions/setup-python@v2 with: # Version range or exact version of a Python version to use, using SemVer's version range syntax. python-version: 3.9.6 # optional, default is 3.x From cc09eb6b707e731e50cde66a3d450809111e2bb8 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 21:38:35 -0500 Subject: [PATCH 069/180] use codecs to decode persistent --- Monika After Story/game/dev/dev_db.rpy | 4 +++- Monika After Story/game/zz_backup.rpy | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/dev/dev_db.rpy b/Monika After Story/game/dev/dev_db.rpy index b87dcfd115..904113745a 100644 --- a/Monika After Story/game/dev/dev_db.rpy +++ b/Monika After Story/game/dev/dev_db.rpy @@ -216,6 +216,7 @@ init python: init python in dev_mas_shared: import pickle + import codecs import store import store.mas_ev_data_ver as ver @@ -257,8 +258,9 @@ init python in dev_mas_shared: # select persistent to load if self.in_char: pkg = store.mas_docking_station.getPackage("persistent") - pdata = pickle.loads(pkg.read().decode("zlib")) + pdata = pickle.loads(codecs.decode(pkg.read(),"zlib")) pkg.close() + else: pdata = store.persistent diff --git a/Monika After Story/game/zz_backup.rpy b/Monika After Story/game/zz_backup.rpy index a35b6b73a3..95fb638227 100644 --- a/Monika After Story/game/zz_backup.rpy +++ b/Monika After Story/game/zz_backup.rpy @@ -20,6 +20,7 @@ default persistent._mas_incompat_per_entered = False python early in mas_per_check: import __main__ import pickle + import codecs import os import datetime import shutil @@ -106,8 +107,8 @@ python early in mas_per_check: """ per_file = None try: - per_file = file(_tp_persistent, "rb") - per_data = per_file.read().decode("zlib") + per_file = open(_tp_persistent, "rb") + per_data = codecs.decode(per_file.read(), "zlib") per_file.close() actual_data = pickle.loads(per_data) From b69e62994502d31f8dc6303ae97a6766aa2771c6 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 22:03:54 -0500 Subject: [PATCH 070/180] :aboom: sripts.rpa --- .github/workflows/mas_check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 000fed9110..bec332cb44 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -58,6 +58,7 @@ jobs: wget https://s3-us-west-2.amazonaws.com/monika-after-story/ddlc/mas.zip mkdir mas0105 unzip mas.zip -d mas0105 + rm mas0105/game/scripts.rpa # copy over gh files to base - name: copy source over From 4e09de0183e65b605f16dbcc0d969ae08c26359d Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 21 Feb 2022 22:05:57 -0500 Subject: [PATCH 071/180] delete this debug line --- Monika After Story/game/zz_hangman.rpy | 1 - 1 file changed, 1 deletion(-) diff --git a/Monika After Story/game/zz_hangman.rpy b/Monika After Story/game/zz_hangman.rpy index 153cda5653..6e9268ced6 100644 --- a/Monika After Story/game/zz_hangman.rpy +++ b/Monika After Story/game/zz_hangman.rpy @@ -255,7 +255,6 @@ init -1 python in mas_hangman: # add the word splitword = line.split(',') - print(splitword) easy_list.append(store.MASPoemWord( splitword[0], float(splitword[1]), From 92c2ec8acdc041efc03034465e5bcc6f85add921 Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 22 Feb 2022 19:38:27 -0500 Subject: [PATCH 072/180] wrap map calls with tuple() --- Monika After Story/game/zz_submods.rpy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index a1f3772728..eb19bf69cf 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -159,7 +159,7 @@ init -991 python in mas_submod_utils: OUT: List of integers representing the version number """ - return map(int, self.version.split('.')) + return tuple(map(int, self.version.split('.'))) def hasUpdated(self): """ @@ -177,7 +177,7 @@ init -991 python in mas_submod_utils: return False try: - old_vers = map(int, old_vers.split('.')) + old_vers = tuple(map(int, old_vers.split('.'))) #Persist data was bad, we'll replace it with something safe and return False as we need not check more except: @@ -257,7 +257,7 @@ init -991 python in mas_submod_utils: NOTE: Does not handle errors as to get here, formats must be correct regardless """ - return map(int, version.split('.')) + return tuple(map(int, version.split('.'))) for submod in submod_map.values(): for dependency, minmax_version_tuple in submod.dependencies.items(): From a0b1891746ac649d99517dc1b10b4afe65d76388 Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 22 Feb 2022 20:24:22 -0500 Subject: [PATCH 073/180] viewkeys -> keys --- Monika After Story/game/zz_selector.rpy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/zz_selector.rpy b/Monika After Story/game/zz_selector.rpy index 4ae1968c3b..d137bdf8a3 100644 --- a/Monika After Story/game/zz_selector.rpy +++ b/Monika After Story/game/zz_selector.rpy @@ -1056,8 +1056,8 @@ init -10 python in mas_selspr: (Default: False) """ if select_type == SELECT_ACS: - old_map_view = old_map.viewkeys() - new_map_view = new_map.viewkeys() + old_map_view = old_map.keys() + new_map_view = new_map.keys() # determine which map is the "old" and which is "new" # we want to remove what is excess from the desired map @@ -1277,8 +1277,8 @@ init -10 python in mas_selspr: map. IN: - old_map_view - viewkeys view of the old map - new_map_view - viewkeys view of the new map + old_map_view - dict_keys view of the old map + new_map_view - dict_keys view of the new map RETURNS: True if the maps are the same, false if different. @@ -3645,8 +3645,8 @@ label mas_selector_sidebar_select(items, select_type, preview_selections=True, o old_select_map = dict(select_map) # also create views that we use for comparisons - old_view = old_select_map.viewkeys() - new_view = select_map.viewkeys() + old_view = old_select_map.keys() + new_view = select_map.keys() # disable menu interactions to prevent bugs disable_esc() From 7399a34da301ec3d986d18cad14ac964427d5f80 Mon Sep 17 00:00:00 2001 From: multimokia Date: Wed, 23 Feb 2022 01:03:02 -0500 Subject: [PATCH 074/180] spritepacks now load --- Monika After Story/game/sprite-chart.rpy | 2 +- Monika After Story/game/zz_spritejsons.rpy | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index a4e9c365c1..ca86c2830a 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -5046,7 +5046,7 @@ init -3 python: # verify other params isbad = False - for prop_name in json_obj.keys(): + for prop_name in tuple(json_obj.keys()): prop_val = json_obj.pop(prop_name) if prop_name in cls.CONS_PARAM_NAMES: if not cls._verify_mpm_item( diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index abee7520cb..80f79a7a34 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -922,12 +922,12 @@ init 189 python in mas_sprites_json: _sel_list = sml.CLOTH_SEL_SL # remvoe from sprite object map - if sp_name in _item_map: + if sp_name in tuple(_item_map.keys()): _item_map.pop(sp_name) if sml.get_sel(sp_obj) is not None: # remove from selectable map - if sp_name in _sel_map: + if sp_name in tuple(_sel_map.keys()): _sel_map.pop(sp_name) # remove from selectable list @@ -1794,7 +1794,7 @@ init 189 python in mas_sprites_json: hair_map = obj_based.pop("hair_map") is_bad = False - for hair_key,hair_value in hair_map.items(): + for hair_key, hair_value in hair_map.items(): # start with type validations # key From 9510b783f33307f9736703e643d937648e55d2f1 Mon Sep 17 00:00:00 2001 From: multimokia Date: Wed, 23 Feb 2022 01:10:16 -0500 Subject: [PATCH 075/180] Fix deco rm. not sure if this is the best fix here... --- Monika After Story/game/zz_backgrounds.rpy | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index d5a9c84bb6..e5709ea70a 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -2235,14 +2235,13 @@ init -10 python: change_info - MASBackgroundChangeInfo object with hides populated. """ - for deco_obj, adv_df, override_tag in self._deco_man.deco_iter_adv(): - + for deco_obj, adv_df, override_tag in tuple(self._deco_man.deco_iter_adv()): if ( - not mas_isDecoTagEnabled(override_tag) - or ( - new_bg is not None - and new_bg.get_deco_info(override_tag) is None - ) + not mas_isDecoTagEnabled(override_tag) + or ( + new_bg is not None + and new_bg.get_deco_info(override_tag) is None + ) ): # hide all deco objects that do not have a definition # in the new bg OR are not in the vis_store From 89438159a66c8292dfc66ccc918dd7899dc3a362 Mon Sep 17 00:00:00 2001 From: multimokia Date: Wed, 23 Feb 2022 10:50:30 -0500 Subject: [PATCH 076/180] another dict pop during iteration --- Monika After Story/game/sprite-chart.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index ca86c2830a..e11b7f5c10 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -3385,7 +3385,7 @@ init -3 python: """ for mux_type in mux_types: acs_with_mux = self._acs_type_map.get(mux_type, {}) - for acs_name in acs_with_mux.keys(): + for acs_name in tuple(acs_with_mux.keys()): self.remove_acs(store.mas_sprites.get_acs(acs_name)) def remove_acs_in(self, accessory, acs_layer): From 894e46bd6074e074e61e50949a1cbdaba4196070 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 25 Aug 2022 21:21:04 -0400 Subject: [PATCH 077/180] `long` no longer exists in py3 --- Monika After Story/game/event-handler.rpy | 1 - 1 file changed, 1 deletion(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 875b66b166..d18670d69f 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -90,7 +90,6 @@ init -999 python in mas_ev_data_ver: bool, int, float, - long, complex, datetime.timedelta, datetime.date, From 01666dfabc8a302e04ef3280806430a29979e7a2 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 25 Aug 2022 21:23:49 -0400 Subject: [PATCH 078/180] builtins --- Monika After Story/game/event-handler.rpy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index d18670d69f..4c52480f2d 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -102,10 +102,10 @@ init -999 python in mas_ev_data_ver: # list types if val_type in ( - __builtin__.list, + __builtins__.list, renpy.python.RevertableList, - __builtin__.set, - __builtin__.frozenset, + __builtins__.set, + __builtins__.frozenset, renpy.python.RevertableSet, tuple, ): From 00e07c327a2f55542cd2c75ee6129758c346417c Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 25 Aug 2022 21:36:12 -0400 Subject: [PATCH 079/180] hippity hoppity, unicode is no longer my property --- Monika After Story/game/event-handler.rpy | 1 - 1 file changed, 1 deletion(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 4c52480f2d..9d41cd4855 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -86,7 +86,6 @@ init -999 python in mas_ev_data_ver: val_type = type(val) if val_type in ( str, - unicode, bool, int, float, From 393beafafce0a078ed40d65b9e54a8d1a33591e9 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 25 Aug 2022 21:37:47 -0400 Subject: [PATCH 080/180] never mind, unicode is my property again --- Monika After Story/game/event-handler.rpy | 1 + 1 file changed, 1 insertion(+) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 9d41cd4855..4c52480f2d 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -86,6 +86,7 @@ init -999 python in mas_ev_data_ver: val_type = type(val) if val_type in ( str, + unicode, bool, int, float, From 51ba4bf9994adb735c0a31039c9f3aa28863e7e9 Mon Sep 17 00:00:00 2001 From: multimokia Date: Thu, 25 Aug 2022 21:51:27 -0400 Subject: [PATCH 081/180] builtins 2, electric boogaloo --- Monika After Story/game/event-handler.rpy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 4c52480f2d..484940789c 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -102,10 +102,10 @@ init -999 python in mas_ev_data_ver: # list types if val_type in ( - __builtins__.list, + builtins.list, renpy.python.RevertableList, - __builtins__.set, - __builtins__.frozenset, + builtins.set, + builtins.frozenset, renpy.python.RevertableSet, tuple, ): @@ -115,7 +115,7 @@ init -999 python in mas_ev_data_ver: return True # dict types - if val_type in (__builtin__.dict, renpy.python.RevertableDict): + if val_type in (builtins.dict, renpy.python.RevertableDict): for sub_key in val: if ( not __strict_can_pickle(sub_key) From 4568d167cceaa7fe84cce1958b90a950266684da Mon Sep 17 00:00:00 2001 From: multimokia Date: Fri, 26 Aug 2022 11:15:49 -0400 Subject: [PATCH 082/180] this option was removed --- Monika After Story/game/sprite-chart.rpy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index a68e5e2d74..acbc86efeb 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -3838,7 +3838,7 @@ init -3 python: def set_acs(self, acs, wear): """ - Basically a single function so callers don't need to + Basically a single function so callers don't need to if-statement-toggle wearing and removal of ACS. IN: @@ -7591,7 +7591,7 @@ init -3 python: Use the functions to modify outfit data as appropriate. Supports: - - preventing ACS from being removed + - preventing ACS from being removed - preventing hair or ACS from being worn """ @@ -7634,7 +7634,7 @@ init -3 python: def set_acs_change_all(self, value): """ Enables or disables ALL ACS changing as part of outfit mode - + IN: value - pass True to enable, False to disable """ @@ -9451,7 +9451,7 @@ python early: width = min(top.width, bottom.width) height = min(top.height, bottom.height) - rv = renpy.display.render.Render(width, height, opaque=not self.alpha) + rv = renpy.display.render.Render(width, height) #opaque=not self.alpha) rv.operation = renpy.display.render.DISSOLVE rv.operation_alpha = self.alpha From 06506407ace7f82d9f2072256e35e3af50e57485 Mon Sep 17 00:00:00 2001 From: multimokia Date: Fri, 26 Aug 2022 11:57:15 -0400 Subject: [PATCH 083/180] migrate glitchtext to MAS level --- Monika After Story/game/definitions.rpy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 11ac4719da..bf7956a73e 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -370,6 +370,16 @@ python early: f = io.BytesIO(self.data) return renpy.display.pgrender.load_image(f, self.filename) + #Stuff we need from base DDLC + nonunicode = "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž" + + def glitchtext(length): + output = "" + for x in range(length): + output += random.choice(nonunicode) + + return output + # uncomment this if you want syntax highlighting support on vim # init -1 python: From c89d4d31e7a1cdd6ac3110bc0f701174fe183029 Mon Sep 17 00:00:00 2001 From: multimokia Date: Fri, 26 Aug 2022 12:45:18 -0400 Subject: [PATCH 084/180] fix substitution --- Monika After Story/game/definitions.rpy | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index bf7956a73e..a71439cdf5 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -187,14 +187,10 @@ python early: obj = getattr(obj, i) else: - # convert the accessor only if obj isn't a dict - # so the accessor is always a long for other iterables - if not isinstance(obj, dict): - i = long(i) - obj = obj[i] - return obj, first + #Fixes an internal renpy change to the convert_field method where it requires a tuple in the first position + return (obj, kwargs), first # allows us to use a more advanced string formatting renpy.substitutions.formatter = MASFormatter() From 703af7bb140ae13263ffe484cf84de92621bbf9f Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 27 Aug 2022 20:03:09 -0400 Subject: [PATCH 085/180] Fix EoF while parsing --- Monika After Story/game/screens.rpy | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 73def61c53..7832b772a4 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -2947,14 +2947,7 @@ screen mas_gen_scrollable_menu(items, display_area, scroll_align, *args): # # OUT: # dict of buttons keys and new values -screen mas_check_scrollable_menu( - items, - display_area, - scroll_align, - selected_button_prompt="Done", - default_button_prompt="Nevermind", - return_all=False -): +screen mas_check_scrollable_menu(items, display_area, scroll_align, selected_button_prompt="Done", default_button_prompt="Nevermind", return_all=False): default buttons_data = { _tuple[1]: { "return_value": _tuple[3] if _tuple[2] else _tuple[4], From 812e7e3cc163f51d4adf2643e5da0b55c3332bb9 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Aug 2022 15:24:22 -0400 Subject: [PATCH 086/180] py3 tools, also simplified. Fix gitignore mess --- .gitignore | 60 +-- tools/gamedir.py | 2 +- tools/ghactions.py | 19 +- tools/menutils.py | 3 +- tools/spritechecker.py | 81 ++- tools/spritemaker.py | 1116 --------------------------------------- tools/spritepuller.py | 362 ------------- tools/testsgenerator.py | 91 ---- tools/toolsmenu.py | 22 +- 9 files changed, 75 insertions(+), 1681 deletions(-) delete mode 100644 tools/spritemaker.py delete mode 100644 tools/spritepuller.py delete mode 100644 tools/testsgenerator.py diff --git a/.gitignore b/.gitignore index 579944055a..96e50d9415 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,30 @@ -*~ -*.rpyc -*.rpyb -*.bak -*.pyc -*.swp -log.txt -traceback.txt -errors.txt -firstrun -*.chr -*.rpa -*.rpa -Monika After Story/game/audio.rpa -*.save -Monika After Story/game/saves/persistent -Monika After Story/IPGuidelines.md -*.rpa +#Catch all rpa, rpyc, rpyb, and rpyms *.rpa -Monika After Story/game/screens.rpyc -Monika After Story/old_persistent.txt -Monika After Story/merged_persistent.txt -Monika After Story/game/screens.rpyc -Monika After Story/project.json +*.rpy[cbm] + +#Catch any persist info +persistent +.bak + +#No compiled pycode or pycache +__pycache__ +*.pyc + +#Ide specific things +*.swp +**/*.vscode + +#Various other clutter-y things +log.txt +traceback.txt +errors.txt +firstrun + +#DDLC specific *.chr -*.rpyc -Monika After Story/project.json -Monika After Story/characters/monika.chr -*.rpyc -Monika After Story/project.json + +#OS Specific files .DS_Store -Monika After Story/.DS_Store -Monika After Story/characters/monika.chr -Monika After Story/update/updates.json -zzzz* -.vscode + +#Toolsmenu things +zzzz* diff --git a/tools/gamedir.py b/tools/gamedir.py index 0502b6ce8c..bdf802d5f1 100644 --- a/tools/gamedir.py +++ b/tools/gamedir.py @@ -1,6 +1,6 @@ # module containing constants about game directory # -# VER: py27 +# VER: py39 REL_PATH_GAME = "../Monika After Story/game/" REL_PATH_DEV = "../Monika After Story/game/dev/" diff --git a/tools/ghactions.py b/tools/ghactions.py index b43fd1e1eb..493388f711 100644 --- a/tools/ghactions.py +++ b/tools/ghactions.py @@ -1,9 +1,5 @@ ## this is for travis to run -# set to True if we are checking sprites for dynamically generated sprites -# False will be standard sprite check behavior -is_dynamic = True - import gamedir as GDIR GDIR.REL_PATH_GAME = "Monika After Story/game/" @@ -18,7 +14,7 @@ #spm.run_gss(sprite_db, sprite_db_keys, quiet=True) # now check sprites -bad_codes = spc.check_sprites(False, is_dynamic) +bad_codes: list[spc.SpriteMismatch] = spc.check_sprites(False) if len(bad_codes) > 0: for bad_code in bad_codes: @@ -30,12 +26,7 @@ ) ) - if is_dynamic: - raise Exception( - "Invalid sprites found. Run sprite checker manually " - "to find invalid sprites." - ) - else: - raise Exception( - "Invalid sprites found. **Did you forget to generate sprites?**" - ) + raise Exception( + "Invalid sprites found. Run sprite checker manually " + "to find invalid sprites." + ) diff --git a/tools/menutils.py b/tools/menutils.py index edc54de582..9179a1ba23 100644 --- a/tools/menutils.py +++ b/tools/menutils.py @@ -15,6 +15,7 @@ import os import platform +from typing import Any HEADER = """\n\n\ #=============================================================================# @@ -44,7 +45,7 @@ __QUIT = "q" -def menu(menu_opts, defindex=None): +def menu(menu_opts: tuple[str, Any], defindex=None) -> Any | None: """ Generates a menu and returns the desired menu action diff --git a/tools/spritechecker.py b/tools/spritechecker.py index 7cec6d667f..550ebe6709 100644 --- a/tools/spritechecker.py +++ b/tools/spritechecker.py @@ -3,15 +3,15 @@ # this will NOT catch issues with non-standard code usage # TODO: add special functions for non-standard usages # -# VER: py27 +# VER: py3.x import os -import spritepuller as spp import gamedir as GDIR import menutils from collections import namedtuple +from dataclasses import dataclass from sprite import StaticSprite # every line of applicable dialogue starts with m and a space @@ -37,14 +37,14 @@ BAD_CODE_FN = "zzzz_badcodes.txt" BAD_CODE_LN = "{1} - FILE:{2} [{0}]" -## namedtuple used to represent sprite codes not found -SpriteMismatch = namedtuple( - "SpriteMismatch", - "code line filename" -) +@dataclass +class SpriteMismatch: + code: str + line: int + filename: str -def check_sprites(inc_dev=False, dynamic=False): +def check_sprites(inc_dev=False) -> list[SpriteMismatch]: """ Goes through every rpy file and checks dialogue and show lines. @@ -53,77 +53,67 @@ def check_sprites(inc_dev=False, dynamic=False): IN: inc_dev - if True, we will check dev files as well (Defualt: False) - dynamic - True means some sprites are generated dynamically - (Default: False) RETURNS: list of SpriteMismatch's """ # sprite dict so we can compare to this # we want a dict for O(1) lookups - sp_dict = spp.pull_sprite_list(as_dict=True) + sp_dict: dict[str, StaticSprite] = dict() # get all the rpys we want to adjust rpys = get_rpy_paths(inc_dev=inc_dev) # go through each rpy and get sprite mms - bad_codes = list() + bad_codes = list[SpriteMismatch]() for rpy in rpys: - bad_codes.extend(check_file(rpy, sp_dict, dynamic)) + bad_codes.extend(check_file(rpy, sp_dict)) return bad_codes -def check_file(fpath, sp_dict, gen_if_missing): +def check_file(fpath: str, sp_dict: dict[str, StaticSprite]) -> list[SpriteMismatch]: """ Checks the given file for sprite code correctness IN: fpath - filepath of the fie to check sp_dict - dict of currently available sprite codes - gen_if_missing - True will attempt to generate the sprite if it is - missing. RETURNS: list of SpriteMismatches, one for every sprite code that was bad """ - sp_mismatches = list() + sp_mismatches = list[SpriteMismatch]() ln_count = 1 - #Loadd spritemap data + #Load spritemap data StaticSprite._loadSpriteMapData() - with open(fpath, "r") as rpy_file: + with open(fpath, "r", encoding='utf-8') as rpy_file: for line in rpy_file: _code = try_extract_code(line.strip()) if _code and _code not in sp_dict: # we have a code but its not in the dict?! - if gen_if_missing: - # attempt to generate if possible - - gen_spr = StaticSprite(_code) - - if gen_spr.invalid: - sp_mismatches.append( - SpriteMismatch(_code, ln_count, fpath) - ) + #let's generate it since sprites are also dynamically generated + gen_spr = StaticSprite(_code) - else: - sp_dict[_code] = gen_spr - - else: + if gen_spr.invalid: sp_mismatches.append( SpriteMismatch(_code, ln_count, fpath) ) + else: + sp_dict[_code] = gen_spr + + ln_count += 1 return sp_mismatches -def extract_dlg_code(line): +def extract_dlg_code(line: str) -> str: """ Extracts the sprite code from the given line Assumes the line is a dlg line or extend line @@ -137,7 +127,7 @@ def extract_dlg_code(line): return line.split(" ")[1] -def extract_code_if_dlg(line): +def extract_code_if_dlg(line: str) -> str | None: """ Does both checking and extraction of a code from a potential dialogue line @@ -161,7 +151,7 @@ def extract_code_if_dlg(line): return None -def extract_shw_code(line): +def extract_shw_code(line: str) -> str: """ Extracts the sprite code from the given line Assumes the line is a show line @@ -175,7 +165,7 @@ def extract_shw_code(line): return line.split(" ")[2] -def extract_code_if_shw(line): +def extract_code_if_shw(line: str) -> str | None: """ Does both checking and extractiong of a code from a potential show line @@ -198,7 +188,7 @@ def extract_code_if_shw(line): return None -def extract_code_if_ext(line): +def extract_code_if_ext(line: str) -> str | None: """ Does both checking and extractiong of a code from a potential extend line @@ -220,7 +210,7 @@ def extract_code_if_ext(line): return None -def get_rpy_paths(inc_dev=False): +def get_rpy_paths(inc_dev=False) -> list[str]: """ Gets a list of all filepaths in teh game dir that are rpy files. Non-recursive @@ -255,7 +245,7 @@ def get_rpy_paths(inc_dev=False): return fp_list -def try_extract_code(cl_line): +def try_extract_code(cl_line: str) -> str | None: """ Attempts to extract a code from a line, using all known ways @@ -279,7 +269,7 @@ def try_extract_code(cl_line): return _code -def write_bad_codes(bad_list): +def write_bad_codes(bad_list: list[SpriteMismatch]) -> None: """ Writes out the bad codes to file @@ -299,7 +289,7 @@ def write_bad_codes(bad_list): ############## special run methods ################################## -def run(): +def run() -> None: """ Runs this module (menu-related) """ @@ -307,7 +297,7 @@ def run(): run_chk(False) -def run_chk(quiet=False, inc_dev=False, use_dyn=False): +def run_chk(quiet=False, inc_dev=False) -> None: """ Main sprite checker workflow @@ -321,12 +311,7 @@ def run_chk(quiet=False, inc_dev=False, use_dyn=False): if inc_dev is None: return - use_dyn = menutils.menu(menu_are_dyn, 1) - - if use_dyn is None: - return - - bad_codes = check_sprites(inc_dev=inc_dev, dynamic=use_dyn) + bad_codes = check_sprites(inc_dev=inc_dev) if len(bad_codes) == 0: # no bad codes diff --git a/tools/spritemaker.py b/tools/spritemaker.py deleted file mode 100644 index 6d64e0ee5b..0000000000 --- a/tools/spritemaker.py +++ /dev/null @@ -1,1116 +0,0 @@ -# makes sprites -# can also load sprites - -import os -import gamedir as GDIR -import menutils - -import spritepuller as spull - -import sprite as spr_module - -from sprite import StaticSprite - -# state vars - -_need_to_gen_sprites = False - -# classes - -class SortedKeySpriteDBIter(): - """ - Iterator over a sprite db. This iterates so that the StaticSprites are - in key order (aka from the given list of keys) - """ - - def __init__(self, sprite_db, sprite_db_keys): - """ - Constructor for this iterator - """ - self.index = -1 - self.sprite_db = sprite_db - self.sprite_db_keys = sprite_db_keys - - # create an empty filter sprite so we dont have crashes during - # iteration - self.__default_fs = FilterSprite() - self.__default_fs.invalid = True - - def __iter__(self): - # This is wrong, but somehow works? - return self - - def next(self): - """ - returns next iteration item - """ - if self.index < len(self.sprite_db_keys)-1: - self.index += 1 - return self.sprite_db.get( - self.sprite_db_keys[self.index], - self.__default_fs - ) - - # otherwise dnoe iterationg - raise StopIteration - - -class FilterSprite(StaticSprite): - """ - A Filter sprite is a version of static sprite used for filtering - other static sprites. - - The primary difference is that any of the initial properties can be - None - """ - POS = "position" - EYE = "eyes" - EYB = "eyebrows" - NSE = "nose" - BLH = "blush" - TRS = "tears" - SWD = "sweat" - EMO = "emote" - MTH = "mouth" - - CLEAR = "CLEAR" - OPTIONAL = "OPTIONAL" - - def __init__(self): - """ - Constructor - """ - self._init_props() - - # setup filter map - self.__filter_set_map = { - self.POS: self.__flt_set_pos, - self.EYE: self.__flt_set_eye, - self.EYB: self.__flt_set_eyb, - self.NSE: self.__flt_set_nse, - self.BLH: self.__flt_set_blh, - self.TRS: self.__flt_set_trs, - self.SWD: self.__flt_set_swd, - self.EMO: self.__flt_set_emo, - self.MTH: self.__flt_set_mth, - } - - # setup eq map - self.__filter_eq_map = { - self.POS: self.__flt_eq_pos, - self.EYE: self.__flt_eq_eye, - self.EYB: self.__flt_eq_eyb, - self.NSE: self.__flt_eq_nse, - self.BLH: self.__flt_eq_blh, - self.TRS: self.__flt_eq_trs, - self.SWD: self.__flt_eq_swd, - self.EMO: self.__flt_eq_emo, - self.MTH: self.__flt_eq_mth, - } - - self._flt_fmt = "{: <12}" - - self.menu_flt_set = [ - ("Set Filters", "Filter: "), - (self.POS.title(), self.POS), - (self.EYE.title(), self.EYE), - (self.EYB.title(), self.EYB), - (self.NSE.title(), self.NSE), - (self.BLH.title(), self.BLH), - (self.TRS.title(), self.TRS), - (self.SWD.title(), self.SWD), - (self.EMO.title(), self.EMO), - (self.MTH.title(), self.MTH), - ] - - def __str__(self): - """ - The string representation of this is a neat thing showing the status - of each filter - """ - return self._status(True, "Filter Settings", True, True) - - def filter(self, otherStaticSprite): - """ - Checks if the given static sprite passes the filter for this one - :param otherStaticSprite: the Static sprite object to compare to - :returns: True if this sprite passes the filter, False if not - """ - if otherStaticSprite.invalid: - return False - - for flt in self.__filter_eq_map.values(): - if not flt(otherStaticSprite): - return False - - return True - - def set_filter(self, category, code): - """ - Sets a filter point - :param category: the filter key to set - :param code: the code to lookup - """ - flt_setter = self.__filter_set_map.get(category, None) - if flt_setter is not None: - flt_setter(code) - - @staticmethod - def build_menu(category): - """ - Builds a menu based on the given category - :param category: one of the class constants - :returns: menu list usable by menutils. May return None if could not - build list - """ - menu = FilterSprite._build_menu(category) - if menu is None: - return None - - # add title - menu.insert(0, (category.title() + " Codes", "Code: ")) - - # append an option to clear the filter - menu.append(("Clear Filter", FilterSprite.CLEAR)) - - return menu - - @staticmethod - def build_selection_menu(category, optional=False, headeradd=""): - """ - Builds a seleciton menu based on the given cateogory - :param category: one of the class constants - :param optional: True will add an optional option, basically skips - setting this. - :param headeradd: add text here to be appended to the header - :returns: menu list usable by menutils. May return None if could not - build list - """ - menu = FilterSprite._build_menu(category) - if menu is None: - return None - - # add title part - menu.insert( - 0, - ("Select " + category.title() + headeradd, "Option: ") - ) - - # add optional - if optional: - menu.append(("No " + category.title(), FilterSprite.OPTIONAL)) - - return menu - - @staticmethod - def from_ss(static_spr): - """ - Generates a FilterSprite object from a StaticSprite - - May return None if invalid static sprite - """ - if static_spr.invalid: - return None - - filter_spr = FilterSprite() - filter_spr.position = static_spr.position - filter_spr.eyes = static_spr.eyes - filter_spr.eyebrows = static_spr.eyebrows - filter_spr.nose = static_spr.nose - filter_spr.blush = static_spr.blush - filter_spr.tears = static_spr.tears - filter_spr.sweatdrop = static_spr.sweatdrop - filter_spr.emote = static_spr.emote - filter_spr.mouth = static_spr.mouth - filter_spr.is_lean = static_spr.is_lean - filter_spr.sides = static_spr.sides - filter_spr.single = static_spr.single - filter_spr.head = static_spr.head - filter_spr.spcode = static_spr.spcode - - return filter_spr - - @staticmethod - def _build_menu(category): - """ - Builds menu options for a category - - May return None if errors occured - """ - is_positions = category == FilterSprite.POS - - selections = FilterSprite._sprite_map.get(category, None) - if selections is None: - return None - - sorted_keys = sorted(selections.keys()) - - menu = [] - - # now the items - for code in sorted_keys: - name = selections[code] - if is_positions and type(name) is not str: - menu.append((StaticSprite.lean_tostring(name), code)) - - else: - menu.append((name, code)) - - return menu - - def _status(self, - useheader, - headerstring, - shownose, - showemote - ): - """ - Builds string representation of this Filter according to given - status props - :param useheader: True will use the block header from menutils, - False will not - :param headerstring: the string to use in the header - :param shownose: True will show the nose part of the filter, False - will not - :param showemote: True will show the emote part of the filter, False - will not - """ - # setup initial strings - if useheader: - msg = [menutils.header(headerstring)] - else: - msg = [self._tab + headerstring] - - # lean and position check - if self.position is None: - position = None - is_lean = None - elif self.is_lean: - position = StaticSprite.lean_tostring(self.position) - is_lean = True - else: - position = self.position - is_lean = self.is_lean - - # now add each filter piece - self.__fmt_flt(msg, "Position:", position) - self.__fmt_flt(msg, "Is Lean:", is_lean) - self.__fmt_flt(msg, "Eyes:", self.eyes) - self.__fmt_flt(msg, "Eyebrows:", self.eyebrows) - if shownose: - self.__fmt_flt(msg, "Nose:", self.nose) - self.__fmt_flt(msg, "Blush:", self.blush) - self.__fmt_flt(msg, "Tears:", self.tears) - self.__fmt_flt(msg, "Sweatdrop:", self.sweatdrop) - if showemote: - self.__fmt_flt(msg, "Emote:", self.emote) - self.__fmt_flt(msg, "Mouth:", self.mouth) - - return "".join(msg) - - def __flt_eq_pos(self, other): - """ - Checks if this position is same as other - :param other: the other static sprite - :returns: False if not None and does not match, True otherwise - """ - if self.position is None: - return True - - return ( - self.position == other.position - and self.is_lean == other.is_lean - ) - - def __flt_eq_eye(self, other): - """ - Checks if this eqyes is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.eyes is None: - return True - - return self.eyes == other.eyes - - def __flt_eq_eyb(self, other): - """ - Checks if this eyebrows is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.eyebrows is None: - return True - - return self.eyebrows == other.eyebrows - - def __flt_eq_nse(self, other): - """ - Checks if this nose is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.nose is None: - return True - - return self.nose == other.nose - - def __flt_eq_blh(self, other): - """ - Checks if this blush is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.blush is None: - return True - - return self.blush == other.blush - - def __flt_eq_trs(self, other): - """ - Checks if this tears is same as other - :param other: the StaticSprite to compare to - "returns: False if not None and does not match, True otherwise - """ - if self.tears is None: - return True - - return self.tears == other.tears - - def __flt_eq_swd(self, other): - """ - Checks if this sweatdrop is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.sweatdrop is None: - return True - - return self.sweatdrop == other.sweatdrop - - def __flt_eq_emo(self, other): - """ - Checks if this emote is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.emote is None: - return True - - return self.emote == other.emote - - def __flt_eq_mth(self, other): - """ - Checks if this mouth is same as other - :param other: the StaticSprite to compare to - :returns: False if not None and does not match, True otherwise - """ - if self.mouth is None: - return True - - return self.mouth == other.mouth - - def __flt_set_pos(self, code): - """ - Sets position filter - :param code: the code to lookup - """ - self.position = self._get_smap(self.POS, code, None) - - if self.position is None: - self.is_lean = None - else: - self.is_lean = self._get_smap("is_lean", code, False) - - def __flt_set_eye(self, code): - """ - Sets eye filter - :param code: the code to lookup - """ - self.eyes = self._get_smap(self.EYE, code, None) - - def __flt_set_eyb(self, code): - """ - Sets eyebrow filter - :param code: the code to lookup - """ - self.eyebrows = self._get_smap(self.EYB, code, None) - - def __flt_set_nse(self, code): - """ - Sets nose filter - :param code: the code to lookup - """ - self.nose = self._get_smap(self.NSE, code, None) - - def __flt_set_blh(self, code): - """ - Sets blush filter - :param code: the code to lookup - """ - self.blush = self._get_smap(self.BLH, code, None) - - def __flt_set_trs(self, code): - """ - Sets tears filter - :param code: the code to lookup - """ - self.tears = self._get_smap(self.TRS, code, None) - - def __flt_set_swd(self, code): - """ - Sets sweatdrop filter - :param code: the code to lookup - """ - self.sweatdrop = self._get_smap(self.SWD, code, None) - - def __flt_set_emo(self, code): - """ - Sets emote filter - :param code: the code to lookup - """ - self.emote = self._get_smap(self.EMO, code, None) - - def __flt_set_mth(self, code): - """ - Sets mouth filter - :param code: the code to lookup - """ - self.mouth = self._get_smap(self.MTH, code, None) - - def __fmt_flt(self, msg_arr, string, value): - """ - adds appropraitely formatted filter string to msg arr - """ - if value is None: - value_str = "" - else: - value_str = str(value) - - msg_arr.extend([ - "\n", - self._tab, - self._flt_fmt.format(string), - value_str - ]) - - -def gen_sprite_files( - sprites, - file_prefix, - file_template, - file_header, - spacing="\n\n", - tostring=str, - quiet=False, - sp_per_file=500, - skip_pause=True, - skip_continue=True -): - """ - Generates sprite files. - - IN: - sprites - the list of sprite objects to generate stuff for - file_prefix - the prefix for each filename - file_template - the template for each filename - file_header - the header to write at the top of each file - spacing - spacing between items - (Default: \n\n) - tostring - to string function to use (must take a sprite object) - (Default: str) - quiet - True will supress menus and stdout - (Default: False) - sp_per_file - max number of sprites allowed per file - (Default: 500) - skip_pause - True will skip pause at end. False will not - (Default: True) - skip_continue - True will skip the continue. False will not - - RETURNS: True if successful, False if abort - """ - # first, check if we will go over the max file limit - if ( int(len(sprites) / sp_per_file) + 1) > spull.MAX_FILE_LIMIT: - # always show error messages - print(MSG_OVER_FILE_LIMIT.format( - len(sprites), - spull.MAX_FILE_LIMIT - )) - return False - - # ask user to continue - if not (quiet or skip_continue): - print(MSG_OVERWRITE.format(file_prefix)) - if not menutils.ask_continue(): - return False - - # setup file counts - file_num = 0 - sp_count = 0 - - # and file data - filename = file_template.format(file_num) - filepath = GDIR.REL_PATH_GAME + filename - - # create thef irst file - if not quiet: - print(MSG_GEN_FILE.format(filename), end="") - output_file = open(os.path.normcase(filepath), "w") - output_file.write(file_header) - - # begin loop over sprites - for sprite_obj in sprites: - - if sp_count >= sp_per_file: - # over the sprites per file limit. we should make new file. - - # increment counts - sp_count = 0 - file_num += 1 - - # close file and say done - output_file.close() - if not quiet: - print("done") - - # setup next file stuff - filename = file_template.format(file_num) - filepath = GDIR.REL_PATH_GAME + filename - - # open file - if not quiet: - print(MSG_GEN_FILE.format(filename), end="") - output_file = open(os.path.normcase(filepath), "w") - output_file.write(file_header) - - # add sprite object to file - output_file.write(tostring(sprite_obj)) - output_file.write(spacing) - sp_count += 1 - - # finally, close the last file and say done - output_file.close() - if not quiet: - print("done") - - if not skip_pause: - menutils.e_pause() - - return True - - -def make_sprite(sprite_db, sprite_db_keys): - """ - Makes a sprite and adds it to the sprite database. - NOTE: keys should be regenerated after this by the caller - - RETURNS: True if sprite creation successful, False if not - """ - sprite_obj = FilterSprite() - sprite_code = [] - - # this is the order we ask for sprites as it is the order of the - # sprite code - sprite_parts = ( - (FilterSprite.POS, False), - (FilterSprite.EYE, False), - (FilterSprite.EYB, False), - # NOTE: we skip nose because there is only 1 -# FilterSprite.NSE, - (FilterSprite.BLH, True), - (FilterSprite.TRS, True), - (FilterSprite.SWD, True), - # NOTE: emote skipped -# FilterSprite.EMO, - (FilterSprite.MTH, False), - ) - - for sp_cat, is_optional in sprite_parts: - sel_not_chosen = True - - # loop until user selection - while sel_not_chosen: - - # generate menu - sel_menu = FilterSprite.build_selection_menu( - sp_cat, - optional=is_optional, - headeradd=" - " + "".join(sprite_code) - ) - - # if optional, we set the default to optional, which is always - # the last item - if is_optional: - defindex = len(sel_menu) - 1 - else: - defindex = None - - # now run teh menu - sel_code = menutils.menu(sel_menu, defindex) - - if sel_code is not None: - # a selection was chosen, check if optinal - - if sel_code != FilterSprite.OPTIONAL: - # actual code selected, update the filter sprite and - # the sprite code list - sprite_code.append(sel_code) - sprite_obj.set_filter(sp_cat, sel_code) - - # mark as selected - sel_not_chosen = False - - else: - # Exit was reached, verify if we actually want to exit - print("\nExiting will abort the creation of this sprite!\n") - if menutils.ask("Discard this sprite"): - return False - - # if we reached here, we should have a sprite now - menutils.clear_screen() - - # lets double check if this is a duplicate - sprite_code = "".join(sprite_code) - if sprite_code in sprite_db: - print("\n\nSprite code {0} already exists! Aborting...".format( - sprite_code - )) - menutils.e_pause() - return False - - # otherwise, no duplicate - # lets show the user and then confirm - print(sprite_obj._status( - True, - "Selected Sprite Settings - " + sprite_code, - False, - False - )) - - # TODO: ask user if they would want to see a preview. Get libpng and - # generate a composite image with the appropraite paths. This is - # really a stretch since exp_previewer covers this already. - - # spacing - print("\n\n") - - # ask to create the sprite - if not menutils.ask("Create sprite"): - print("\nSprite discarded.") - menutils.e_pause() - return False - - # user said yes! - # create the sprite - real_sprite = StaticSprite(sprite_code) - - # now determine if we need an atl variant - atl_sprite = real_sprite.make_atl() - - # print and abort if errors occured - if real_sprite.invalid or (atl_sprite is not None and atl_sprite.invalid): - menutils.clear_screen() - print("\n\nError making this sprite. Notify devs to fix.") - menutils.e_pause() - return False - - # otherwise we ok - sprite_db[real_sprite.spcode] = real_sprite - - if atl_sprite is not None: - sprite_db[atl_sprite.spcode] = atl_sprite - - return True - - -def make_sprite_bc(sprite_db, sprite_db_keys): - """ - Makes sprite using just a code and adds it to sprite database. - NOTE: keys should be regenerated after this by the caller - - RETURNS: True if sprite creation successful, False if not - """ - not_valid_code = True - sprite_created = False - while not_valid_code: - menutils.clear_screen() - print("\n\n") - trycode = input("Enter a sprite code: ") - - # build a static sprite with the code - new_sprite = StaticSprite(trycode) - - # and atl version - atl_sprite = new_sprite.make_atl() - - if new_sprite.invalid or (atl_sprite is not None and atl_sprite.invalid): - # if invalid, ask user if they want to continue - print("\nSprite code {0} is invalid.\n".format(trycode)) - if not menutils.ask("Try again", def_no=False): - return sprite_created - - elif new_sprite.spcode in sprite_db: - # check if already exists - print("\nSprite code {0} already exists!\n".format( - new_sprite.spcode - )) - if not menutils.ask("Try again", def_no=False): - return sprite_created - - else: - # valid sprite, means we should show it and ask for confirm - filter_spr = FilterSprite.from_ss(new_sprite) - print(filter_spr._status( - True, - "Selected Sprite Settings - " + new_sprite.spcode, - True, - True - )) - - # spacing - print("\n\n") - - # ask to create the sprite - if not menutils.ask("Create sprite"): - print("\nSprite discarded.\n") - - else: - # user said yes! - # add sprite to db and prompt for more - sprite_db[new_sprite.spcode] = new_sprite - - if atl_sprite is not None: - sprite_db[atl_sprite.spcode] = atl_sprite - - sprite_created = True - print("\nSprite created.\n") - - if not menutils.ask("Create another sprite", def_no=False): - return sprite_created - - -### runners - -def run(): - """ - Runs this module (menu related) - """ - # first load all sprites - print("Loading sprites...", end="") - sprite_db = _load_sprites() - - # abort if failed - if sprite_db is None: - print("\nERROR in loading sprites. Aborting...") - return - - # now sort keys - sprite_db_keys = sorted(sprite_db.keys()) - - # otherwise success - print("DONE") - - choice = True - while choice is not None: - - # set apropriate title text - if _need_to_gen_sprites: - title_entry = ("Sprite Maker" + MSG_UNSAVED, "Option: ") - else: - title_entry = ("Sprite Maker", "Option: ") - - menu_main[0] = title_entry - - choice = menutils.menu(menu_main) - - if choice is not None: - result = choice(sprite_db, sprite_db_keys) - - # only make sprite returns a value, which is the updated keys - # list - if result is not None: - sprite_db_keys = result - - elif _need_to_gen_sprites: - # user hit None, but we should make sure that they wanted to leave - # without saving changes - menutils.clear_screen() - print("\n\n" + MSG_WARN_GEN) - if not menutils.ask("Leave this menu"): - choice = True - - -def run_gss(sprite_db, sprite_db_keys, quiet=False, sp_per_file=500): - """ - Generates static sprites, and alises - - IN: - quiet - supresses menus and stdout - sp_per_file - max number of sprites allowed per file - """ - # ask for draw function to use - if not quiet: - df_choice = True - while df_choice is not None: - df_choice = menutils.menu(menu_sdf, defindex=1) - - # if no choice was made here (or we aborted), then quit - if df_choice is None: - return - - # otherwise set and quit loop - spr_module.draw_function = df_choice - df_choice = None - - # ask if okay to overwrite files - if not quiet: - print("\n" + MSG_OVERWRITE.format( - ", ".join([ - spull.STATIC_PREFIX, - spull.ALIAS_PREFIX, - spull.ATL_PREFIX - ]) - )) - if not menutils.ask_continue(): - return - - # generate static sprites - if not gen_sprite_files( - list(SortedKeySpriteDBIter(sprite_db, sprite_db_keys)), - spull.STATIC_PREFIX, - spull.STATIC_TEMPLATE, - __SP_STATIC_HEADER, - quiet=quiet, - sp_per_file=sp_per_file - ): - return - - # now for filter sprites - if not gen_sprite_files( - filter( - StaticSprite.as_is_closed_eyes, - SortedKeySpriteDBIter(sprite_db, sprite_db_keys) - ), - spull.ALIAS_PREFIX, - spull.ALIAS_TEMPLATE, - __SP_STATIC_HEADER, - spacing="\n", - tostring=StaticSprite.as_alias_static, - quiet=quiet, - sp_per_file=5000 - ): - return - - # and finally atl sprites - if not gen_sprite_files( - filter( - StaticSprite.as_is_not_closed_eyes, - SortedKeySpriteDBIter(sprite_db, sprite_db_keys) - ), - spull.ATL_PREFIX, - spull.ATL_TEMPLATE, - __SP_STATIC_HEADER, - tostring=StaticSprite.as_atlify, - quiet=quiet, - sp_per_file=sp_per_file - ): - return - - # done, print done - if not quiet: - menutils.e_pause() - - global _need_to_gen_sprites - _need_to_gen_sprites = False - - -def run_mkspr(sprite_db, sprite_db_keys): - """ - Makes a sprite. - - Returns an updated sprite_db_keys, or None if no changes - """ - if make_sprite(sprite_db, sprite_db_keys): - # success we made a sprite - - # mark that we are dirty and need to regen - global _need_to_gen_sprites - _need_to_gen_sprites = True - - # return updated keys - return sorted(sprite_db.keys()) - - return None - - -def run_mkspr_bc(sprite_db, sprite_db_keys): - """ - Makes a sprite. - - Returns an updated sprite_db_keys, or None if no changes - """ - if make_sprite_bc(sprite_db, sprite_db_keys): - # we made some sprites - - # mark that we are dirty and need to regin - global _need_to_gen_sprites - _need_to_gen_sprites = True - - # return updated keys - return sorted(sprite_db.keys()) - - return None - - -def run_lstc(sprite_db, sprite_db_keys): - """ - List codes submenu - """ - choice = True - ss_filter = FilterSprite() - - while choice is not None: - choice = menutils.menu(menu_lstc) - - if choice is not None: - choice(sprite_db, sprite_db_keys, ss_filter) - - -def run_lstc_show(sprite_db, sprite_db_keys, ss_filter): - """ - Show sprites, based on filter - """ - # filter valid sprites - filtered = filter( - ss_filter.filter, - SortedKeySpriteDBIter(sprite_db, sprite_db_keys) - ) - - # show codes - menutils.paginate( - "Sprite Codes", - filtered, - str_func=StaticSprite.as_scstr_code - ) - - -def run_lstc_showfilter(sprite_db, sprite_db_keys, ss_filter): - """ - Show filter settings - """ - menutils.clear_screen() - print(str(ss_filter)) - menutils.e_pause() - - -def run_lstc_setfilter(sprite_db, sprite_db_keys, ss_filter): - """ - Set filter settings - """ - choice = True - - while choice is not None: - choice = menutils.menu(ss_filter.menu_flt_set) - - if choice is not None: - # get a menu baesd on the category - category_menu = FilterSprite.build_menu(choice) - if category_menu is not None: - code = menutils.menu(category_menu) - - # set if not none - if code is not None: - ss_filter.set_filter(choice, code) - - -############### menus ############ - -menu_main = [ - ("Sprite Maker", "Option: "), - ("List Codes", run_lstc), - ("Make Sprite (Interactive)", run_mkspr), - ("Make Sprite (By Code)", run_mkspr_bc), - ("Generate Sprites", run_gss), -] - -menu_lstc = [ - ("Filter Options", "Option: "), - ("Show List", run_lstc_show), - ("Show Filters", run_lstc_showfilter), - ("Set Filter", run_lstc_setfilter), -] - -menu_sdf = [ - ("Set Draw Function", "Option: "), - ( - "Image Manipulators (" + spr_module.DRAW_MONIKA_IM + ")", - spr_module.DRAW_MONIKA_IM - ), - ( - "Sprite Strings (" + spr_module.DRAW_MONIKA + ")", - spr_module.DRAW_MONIKA - ), -] - -# strings - -MSG_OVERWRITE = ( - "This will overwrite all sprite chart files that start with:\n {0}\n" -) - -MSG_OVER_FILE_LIMIT = "\nCannot fit {0} sprites into {1} files. Aborting..." -MSG_GEN_FILE = "Generating file '{0}'..." -MSG_WARN_GEN = ( - "WARNING! You have created a sprite but have not regenerated the sprite " - "charts.\nLeaving this menu will abort your changes.\n" -) -MSG_UNSAVED = " - **Run Generate Sprites to save changes**" - -__SP_STATIC_HEADER = """\ -############################ AUTO-GENERATED ################################### -## DO NOT EDIT THIS FILE ## -## ## -## This was auto-generated by the the spritemaker tool ## -############################################################################### - -""" - -# internal functions - - -def _load_sprites(): - """ - Loads sprite code data so this module can use it. - NOTE: if None is returnd, treat as failure - :returns: dictionary of the following format: - [0] - sprite code (without static) - [1] - StaticSprite object - """ - sprite_list = [] - - # load all static sprites - for sprite_filepath in spull.STATIC_CHARTS: - with open(os.path.normcase(sprite_filepath), "r") as sprite_file: - sprite_list.extend(spull.pull_sprite_list_from_file( - sprite_file, - True - )) - - # generate dict of static sprites - sprite_db = {} - for sprite_code in sprite_list: - sprite_obj = StaticSprite(sprite_code) - - # immediately quit if invalid - if sprite_obj.invalid: - return None - - # otherwise add - sprite_db[sprite_code] = sprite_obj - - # make as atl if possible - atl_sprite = sprite_obj.make_atl() - if atl_sprite is not None: - sprite_db[atl_sprite.spcode] = atl_sprite - - return sprite_db diff --git a/tools/spritepuller.py b/tools/spritepuller.py deleted file mode 100644 index 01885be7df..0000000000 --- a/tools/spritepuller.py +++ /dev/null @@ -1,362 +0,0 @@ -## module with a function that pull sprites out of the sprite-chart -# -# VER: Python 2.7 - -import os -import gamedir as GDIR -import menutils - -STATIC_PREFIX = "sprite-chart-0" -ALIAS_PREFIX = "sprite-chart-1" -ATL_PREFIX = "sprite-chart-2" - -STATIC_TEMPLATE = STATIC_PREFIX + "{0}.rpy" -ALIAS_TEMPLATE = ALIAS_PREFIX + "{0}.rpy" -ATL_TEMPLATE = ATL_PREFIX + "{0}.rpy" - -STATIC_CHARTS = [] -ALIAS_CHARTS = [] -ATL_CHARTS = [] - -SPRITE_PATH = [ - GDIR.REL_PATH_GAME + "sprite-chart.rpy", -] - -SAVE_PATH = "zzzz_spritelist" -SAVE_PATH_D = "zzzz_spritedict" -SAVE_PATH_IO = GDIR.REL_PATH_GAME + "zzzz_sprite_opt.rpy" - -IMG_START = "image monika" -IMG_OUT = '"monika {0}"' -IMG_OUT_L = '\n "monika {0}",' - -DYN_DIS = "DynamicDisplayable" - -MAX_FILE_LIMIT = 10 - - -def clean_sprite(code): - """ - Cleans the given sprite (removes excess whitespace, colons) - - IN: - code - sprite code to clean - - RETURNS: - cleaned sprite code - """ - code = code.strip() - return code.replace(":","") - - -def is_dyn_line(line): - """ - Checks if the given line is a sprite line with dynamic displayable - :param line: line to check - :returns: True if the given line is a sprite line, False otherwise. - """ - return is_sprite_line(line) and DYN_DIS in line - - -def is_sprite_line(line): - """ - Checks if the given line is a sprite line - - NOTE: a sprite line is a line that starts with "image monika" - - NOTE: does not strip the given line. - - IN: - line - line to check - - RETURNS: - True if the given line is a sprite line, False otherwise. - """ - return line.startswith(IMG_START) - - -def pull_dyn_sprite_code(line): - """ - Pulls sprite code from teh given line. - Only ones that are monika + dynamic displayable are allowed - NOTE: this also removes the _static lines - :param line: line topull sprite code - :returns: the sprite code we got, or None if not found - """ - if is_dyn_line(line): - spcode = clean_sprite(line.split(" ")[2]) - return spcode.partition("_")[0] - - return None - - -def pull_sprite_code(line): - """ - Pulls the sprite code from the given line. - This checks if the given line is a sprite line before pulling the code. - - IN: - line - line to pull sprite code - - RETURNS: the sprite code we got, if not a sprite code, we return None - """ - if is_sprite_line(line): - return clean_sprite(line.split(" ")[2]) - - return None - - -def pull_sprite_list(as_dict=False): - """ - Goes through the sprite chart and generates a list of all the known sprite - codes. - - NOTE: - This does not really do any filtering, so if sprite-chart has excess - images, they will be reflected here. - - IN: - as_dict - if True, this will return a dict with the known sprite codes - as keys. The values will be 0. - - RETURNS: - list of known sprite codes, or dict if as_dict is True - """ - sprite_list = list() - - for sprfile in SPRITE_PATH: - with open(os.path.normcase(sprfile), "r") as sprite_file: - sprite_list.extend(pull_sprite_list_from_file(sprite_file)) - - if as_dict: - # do we want a dict instead? - sprite_dict = dict() - - for sprite in sprite_list: - sprite_dict[sprite] = 0 - - return sprite_dict - - # otherwise we want the lsit - return sprite_list - - -def pull_sprite_list_from_file(sprite_file, dyn_only=False): - """ - Pulls a list of sprite from the given file - :param sprite_file: file object to read sprites from - :param dyn_only: True will only get the dynamic displayable sprites, False - will get all - :returns: list of sprite codes - """ - if dyn_only: - puller = pull_dyn_sprite_code - else: - puller = pull_sprite_code - - sprite_list = [] - - for line in sprite_file: - code = puller(line) - - if code: - sprite_list.append(code) - - return sprite_list - - -def write_spritecodes(sprites): - """ - Writes out a sprite file that just contains each sprite code out, - one sprite code per line - - IN: - sprites - list of sprite codes to write out. - """ - with open(os.path.normcase(SAVE_PATH), "w") as outfile: - for line in sprites: - outfile.write(line + "\n") - - -def write_spritestats(sprites): - """ - Writes out a sprite file that just contains each sprite code with its - value, one sprite code per line - - IN: - sprites - dict of sprite codes to write out - """ - with open(os.path.normcase(SAVE_PATH_D), "w") as outfile: - for code in sprites: - outfile.write("{0}: {1}\n".format(code, sprites[code])) - - -def write_zz_sprite_opt(sprites): - """ - Writes out a sprite file that can be used in renpy to optimize sprites - using image prediction. - - IN: - sprites - list of sprite codes to write out. - """ - with open(os.path.normcase(SAVE_PATH_IO), "w") as outfile: - outfile.write(__ZZ_SP_OPT_HEADER) - - open_list = False - - # we must write in pieces beause a giant list is too long - for index in range(len(sprites)): - # write header every 100 - if index % 100 == 0: - open_list = True - outfile.write(__ZZ_SP_OPT_LINE_START) - - # now actual line - outfile.write(IMG_OUT_L.format(sprites[index])) - - # on the 100th item, write footer - if index % 100 == 99: - open_list = False - outfile.write(__ZZ_SP_OPT_LINE_END) - - # 1 last footer needed - if open_list: - outfile.write(__ZZ_SP_OPT_LINE_END) - - outfile.write(__ZZ_SP_OPT_FOOTER) - - -####################### special run methods ################################## - - -def run(): - """ - Runs this module (menu-related) - """ - choice = menutils.menu(menu_main) - - if choice is None: - return - - # otherwise we have a choice - choice() - - -def run_spl(quiet=False): - """ - Generates the sprite code list and writes it to file - - IN: - quiet - True will suppress output - """ - if not quiet: - print("Generating sprite list....") - - sp_list = pull_sprite_list() - - if not quiet: - print("Writing sprite list to " + SAVE_PATH) - - write_spritecodes(sp_list) - - if not quiet: - print("\nDone") - menutils.e_pause() - - -def run_rpy_all(quiet=False): - """ - Generates optimized image rpy for ALL images - - IN: - quiet - True will suppress output - """ - if not quiet: - print("Generating sprite list....") - - sp_list = pull_sprite_list() - - if not quiet: - print("Writing optimized rpy to " + SAVE_PATH_IO) - - write_zz_sprite_opt(sp_list) - - if not quiet: - print("\nDone") - menutils.e_pause() - - -################### menus ################# - -menu_main = [ - ("Sprite Puller", "Option: "), - ("Generate Sprite Code List", run_spl), - ("Generate Optimized RPY", run_rpy_all) -] - - -#### strings for formatting: -__ZZ_SP_OPT_HEADER = """\ -############################ AUTO-GENERATED ################################### -## DO NOT EDIT THIS FILE ## -## ## -## This was auto-generated by the spritepuller tool ## -############################################################################### -# -# This is a module designed for optimizing sprites by predicting them. -# NOTE: memory usage increases massively when this is used. -# USE AT YOUR OWN RISK -# - -init 2020 python: - image_list = [] -""" - -__ZZ_SP_OPT_LINE_START = """\ - image_list.extend([""" - -__ZZ_SP_OPT_LINE_END = """ - ]) -""" - -__ZZ_SP_OPT_FOOTER = """ - renpy.start_predict(*image_list) -""" - -# startup stuff - -def _find_files(prefix): - """ - Generates list of all the files with a given prefix - This also adds the appropriate path - """ - spr_files = os.listdir(os.path.normcase(GDIR.REL_PATH_GAME)) - - return [ - GDIR.REL_PATH_GAME + filename - for filename in spr_files - if filename.startswith(prefix) - ] - - -def _init(): - """ - Startup - """ - # get files - static_files = _find_files(STATIC_PREFIX) - alias_files = _find_files(ALIAS_PREFIX) - atl_files = _find_files(ATL_PREFIX) - - # add to individual lists - STATIC_CHARTS.extend(static_files) - ALIAS_CHARTS.extend(alias_files) - ATL_CHARTS.extend(atl_files) - - # add to sprite path - SPRITE_PATH.extend(static_files) - SPRITE_PATH.extend(alias_files) - SPRITE_PATH.extend(atl_files) - - -_init() diff --git a/tools/testsgenerator.py b/tools/testsgenerator.py deleted file mode 100644 index c023a229f7..0000000000 --- a/tools/testsgenerator.py +++ /dev/null @@ -1,91 +0,0 @@ -# module that generates expression test files - -import spritepuller as spp -import gamedir as GDIR - -import menutils - -# bad code file name -EXPR_TEST_FN = "zzzz_dev_exprtest.rpy" - - -def generate_expr_test(quiet=False): - """ - Uses the expressions retrieved from the spritepuller utility, - then cleans them(remove duplicates, remove old expressions), - sorts them and finally generates a file named zzzz_dev_exprtest.rpy - which adds an event which can be accessed in-game to check every single - expression added - - IN: - quiet - True will suppress output - """ - if not quiet: - print("\nPulling Sprites ...") - # pull the sprites and put them in a set to remove duplicates - sp_list = set(spp.pull_sprite_list()) - - # pick only the new ones - sp_list = [x for x in sp_list if len(x) > 3] - - # sort them - sp_list.sort() - - if not quiet: - print("\nWriting to file ...") - - with open(GDIR.REL_PATH_DEV+ EXPR_TEST_FN, "w") as outfile: - - # write the header - outfile.write(__ZZ_EXPR_TEST_HEADER) - - for sp in sp_list: - - # write each line format will be 'm expr_code "expr_code"' - outfile.write(__ZZ_EXPR_TEST_LINE.format(sp) + "\n") - - # write the footer - outfile.write(__ZZ_EXPR_TEST_FOOTER) - - if not quiet: - print("\nDone") - menutils.e_pause() - -def run(): - """ - Runs this module (menu-related) - """ - # for now, we only have 1 workflow - generate_expr_test() - - - -#### strings for formatting: -__ZZ_EXPR_TEST_HEADER = """ -############################ AUTO-GENERATED ################################### -## DO NOT EDIT THIS FILE ## -## ## -## This was auto-generated by the testsgenerator tool ## -############################################################################### -# -# This is a module designed for running test on all the current expressions. -# NOTE: This is going to be a click nightmare. -# USE AT YOUR OWN RISK -# - -init 5 python: - addEvent( - Event( - persistent.event_database, - eventlabel="dev_expr_testing", - category=["dev"], - prompt="TEST EXPRESSIONS", - pool=True, - unlocked=True - ) - ) - -label dev_expr_testing: -""" -__ZZ_EXPR_TEST_LINE = ' m {0} "{0}"' -__ZZ_EXPR_TEST_FOOTER = " return" diff --git a/tools/toolsmenu.py b/tools/toolsmenu.py index 21b46ae1d2..071e1affd3 100644 --- a/tools/toolsmenu.py +++ b/tools/toolsmenu.py @@ -1,38 +1,30 @@ ## TODO ## we need a neato menu for everything # -# VER: python 2.7 +# VER: python 3.x import os __clean_path = os.getcwd().replace("\\", "/") if "MonikaModDev/tools" not in __clean_path: os.chdir("tools") -try: - raw_input -except NameError: - print("run this using py2") - exit(1) - -import spritepuller as spp +# import spritepuller as spp import spritechecker as spc -import spritemaker as spm -import testsgenerator as tg +# import spritemaker as spm +# import testsgenerator as tg import menutils menu_main = [ ("MAS Dev Tools", "Utility: "), - ("Sprite Puller", spp.run), + # ("Sprite Puller", spp.run), ("Check Sprites", spc.run), - ("Make Sprites", spm.run), - ("Generate Expressions Test", tg.run) + # ("Make Sprites", spm.run), + # ("Generate Expressions Test", tg.run) ] choice = True while choice is not None: - choice = menutils.menu(menu_main) - if choice is not None: choice() From 1e5db900cab5a15b83fd54cc22afd428fd536899 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Aug 2022 17:08:31 -0400 Subject: [PATCH 087/180] py310 --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index bec332cb44..73faadac18 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -33,7 +33,7 @@ jobs: uses: actions/setup-python@v2 with: # Version range or exact version of a Python version to use, using SemVer's version range syntax. - python-version: 3.9.6 # optional, default is 3.x + python-version: 3.10.4 # optional, default is 3.x # dl renpy src - name: Download rpy source From 29048958965b02d8b8a180e21897b4ca13651c08 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Aug 2022 21:28:26 -0400 Subject: [PATCH 088/180] whoops, reinstate this --- tools/spritechecker.py | 3 +- tools/spritepuller.py | 364 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 tools/spritepuller.py diff --git a/tools/spritechecker.py b/tools/spritechecker.py index 550ebe6709..5da9d3d081 100644 --- a/tools/spritechecker.py +++ b/tools/spritechecker.py @@ -13,6 +13,7 @@ from collections import namedtuple from dataclasses import dataclass from sprite import StaticSprite +from spritepuller import pull_sprite_list # every line of applicable dialogue starts with m and a space DLG_START = "m " @@ -59,7 +60,7 @@ def check_sprites(inc_dev=False) -> list[SpriteMismatch]: """ # sprite dict so we can compare to this # we want a dict for O(1) lookups - sp_dict: dict[str, StaticSprite] = dict() + sp_dict: dict[str, StaticSprite] = pull_sprite_list(True) # get all the rpys we want to adjust rpys = get_rpy_paths(inc_dev=inc_dev) diff --git a/tools/spritepuller.py b/tools/spritepuller.py new file mode 100644 index 0000000000..f4821c0dc1 --- /dev/null +++ b/tools/spritepuller.py @@ -0,0 +1,364 @@ +## module with a function that pull sprites out of the sprite-chart +# +# VER: Python 2.7 + +import os +import gamedir as GDIR +from spritechecker import StaticSprite +import menutils +from typing import IO + +STATIC_PREFIX = "sprite-chart-0" +ALIAS_PREFIX = "sprite-chart-1" +ATL_PREFIX = "sprite-chart-2" + +STATIC_TEMPLATE = STATIC_PREFIX + "{0}.rpy" +ALIAS_TEMPLATE = ALIAS_PREFIX + "{0}.rpy" +ATL_TEMPLATE = ATL_PREFIX + "{0}.rpy" + +STATIC_CHARTS = [] +ALIAS_CHARTS = [] +ATL_CHARTS = [] + +SPRITE_PATH = [ + GDIR.REL_PATH_GAME + "sprite-chart.rpy", +] + +SAVE_PATH = "zzzz_spritelist" +SAVE_PATH_D = "zzzz_spritedict" +SAVE_PATH_IO = GDIR.REL_PATH_GAME + "zzzz_sprite_opt.rpy" + +IMG_START = "image monika" +IMG_OUT = '"monika {0}"' +IMG_OUT_L = '\n "monika {0}",' + +DYN_DIS = "DynamicDisplayable" + +MAX_FILE_LIMIT = 10 + + +def clean_sprite(code: str) -> str: + """ + Cleans the given sprite (removes excess whitespace, colons) + + IN: + code - sprite code to clean + + RETURNS: + cleaned sprite code + """ + code = code.strip() + return code.replace(":","") + + +def is_dyn_line(line: str) -> bool: + """ + Checks if the given line is a sprite line with dynamic displayable + :param line: line to check + :returns: True if the given line is a sprite line, False otherwise. + """ + return is_sprite_line(line) and DYN_DIS in line + + +def is_sprite_line(line: str) -> bool: + """ + Checks if the given line is a sprite line + + NOTE: a sprite line is a line that starts with "image monika" + + NOTE: does not strip the given line. + + IN: + line - line to check + + RETURNS: + True if the given line is a sprite line, False otherwise. + """ + return line.startswith(IMG_START) + + +def pull_dyn_sprite_code(line: str) -> str | None: + """ + Pulls sprite code from teh given line. + Only ones that are monika + dynamic displayable are allowed + NOTE: this also removes the _static lines + :param line: line topull sprite code + :returns: the sprite code we got, or None if not found + """ + if is_dyn_line(line): + spcode = clean_sprite(line.split(" ")[2]) + return spcode.partition("_")[0] + + return None + + +def pull_sprite_code(line: str) -> str | None: + """ + Pulls the sprite code from the given line. + This checks if the given line is a sprite line before pulling the code. + + IN: + line - line to pull sprite code + + RETURNS: the sprite code we got, if not a sprite code, we return None + """ + if is_sprite_line(line): + return clean_sprite(line.split(" ")[2]) + + return None + + +def pull_sprite_list(as_dict=False) -> dict[str, StaticSprite] | list[StaticSprite]: + """ + Goes through the sprite chart and generates a list of all the known sprite + codes. + + NOTE: + This does not really do any filtering, so if sprite-chart has excess + images, they will be reflected here. + + IN: + as_dict - if True, this will return a dict with the known sprite codes + as keys. The values will be 0. + + RETURNS: + list of known sprite codes, or dict if as_dict is True + """ + sprite_list: list[StaticSprite] = list() + + for sprfile in SPRITE_PATH: + with open(os.path.normcase(sprfile), "r", encoding="utf-8") as sprite_file: + sprite_list.extend(pull_sprite_list_from_file(sprite_file)) + + if as_dict: + # do we want a dict instead? + sprite_dict: dict[str, StaticSprite] = dict() + + for sprite in sprite_list: + sprite_dict[sprite] = 0 + + return sprite_dict + + # otherwise we want the lsit + return sprite_list + + +def pull_sprite_list_from_file(sprite_file: IO, dyn_only=False): + """ + Pulls a list of sprite from the given file + :param sprite_file: file object to read sprites from + :param dyn_only: True will only get the dynamic displayable sprites, False + will get all + :returns: list of sprite codes + """ + if dyn_only: + puller = pull_dyn_sprite_code + else: + puller = pull_sprite_code + + sprite_list = [] + + for line in sprite_file: + code = puller(line) + + if code: + sprite_list.append(code) + + return sprite_list + + +def write_spritecodes(sprites: list[str]) -> None: + """ + Writes out a sprite file that just contains each sprite code out, + one sprite code per line + + IN: + sprites - list of sprite codes to write out. + """ + with open(os.path.normcase(SAVE_PATH), "w", encoding="utf-8") as outfile: + for line in sprites: + outfile.write(line + "\n") + + +def write_spritestats(sprites: dict[str, str]) -> None: + """ + Writes out a sprite file that just contains each sprite code with its + value, one sprite code per line + + IN: + sprites - dict of sprite codes to write out + """ + with open(os.path.normcase(SAVE_PATH_D), "w", encoding="utf-8") as outfile: + for code in sprites: + outfile.write("{0}: {1}\n".format(code, sprites[code])) + + +def write_zz_sprite_opt(sprites: list[str]) -> None: + """ + Writes out a sprite file that can be used in renpy to optimize sprites + using image prediction. + + IN: + sprites - list of sprite codes to write out. + """ + with open(os.path.normcase(SAVE_PATH_IO), "w", encoding="utf-8") as outfile: + outfile.write(__ZZ_SP_OPT_HEADER) + + open_list = False + + # we must write in pieces beause a giant list is too long + for index in range(len(sprites)): + # write header every 100 + if index % 100 == 0: + open_list = True + outfile.write(__ZZ_SP_OPT_LINE_START) + + # now actual line + outfile.write(IMG_OUT_L.format(sprites[index])) + + # on the 100th item, write footer + if index % 100 == 99: + open_list = False + outfile.write(__ZZ_SP_OPT_LINE_END) + + # 1 last footer needed + if open_list: + outfile.write(__ZZ_SP_OPT_LINE_END) + + outfile.write(__ZZ_SP_OPT_FOOTER) + + +####################### special run methods ################################## + + +def run() -> None: + """ + Runs this module (menu-related) + """ + choice = menutils.menu(menu_main) + + if choice is None: + return + + # otherwise we have a choice + choice() + + +def run_spl(quiet=False) -> None: + """ + Generates the sprite code list and writes it to file + + IN: + quiet - True will suppress output + """ + if not quiet: + print("Generating sprite list....") + + sp_list = pull_sprite_list() + + if not quiet: + print("Writing sprite list to " + SAVE_PATH) + + write_spritecodes(sp_list) + + if not quiet: + print("\nDone") + menutils.e_pause() + + +def run_rpy_all(quiet=False) -> None: + """ + Generates optimized image rpy for ALL images + + IN: + quiet - True will suppress output + """ + if not quiet: + print("Generating sprite list....") + + sp_list = pull_sprite_list() + + if not quiet: + print("Writing optimized rpy to " + SAVE_PATH_IO) + + write_zz_sprite_opt(sp_list) + + if not quiet: + print("\nDone") + menutils.e_pause() + + +################### menus ################# + +menu_main = [ + ("Sprite Puller", "Option: "), + ("Generate Sprite Code List", run_spl), + ("Generate Optimized RPY", run_rpy_all) +] + + +#### strings for formatting: +__ZZ_SP_OPT_HEADER = """\ +############################ AUTO-GENERATED ################################### +## DO NOT EDIT THIS FILE ## +## ## +## This was auto-generated by the spritepuller tool ## +############################################################################### +# +# This is a module designed for optimizing sprites by predicting them. +# NOTE: memory usage increases massively when this is used. +# USE AT YOUR OWN RISK +# + +init 2020 python: + image_list = [] +""" + +__ZZ_SP_OPT_LINE_START = """\ + image_list.extend([""" + +__ZZ_SP_OPT_LINE_END = """ + ]) +""" + +__ZZ_SP_OPT_FOOTER = """ + renpy.start_predict(*image_list) +""" + +# startup stuff + +def _find_files(prefix): + """ + Generates list of all the files with a given prefix + This also adds the appropriate path + """ + spr_files = os.listdir(os.path.normcase(GDIR.REL_PATH_GAME)) + + return [ + GDIR.REL_PATH_GAME + filename + for filename in spr_files + if filename.startswith(prefix) + ] + + +def _init() -> None: + """ + Startup + """ + # get files + static_files = _find_files(STATIC_PREFIX) + alias_files = _find_files(ALIAS_PREFIX) + atl_files = _find_files(ATL_PREFIX) + + # add to individual lists + STATIC_CHARTS.extend(static_files) + ALIAS_CHARTS.extend(alias_files) + ATL_CHARTS.extend(atl_files) + + # add to sprite path + SPRITE_PATH.extend(static_files) + SPRITE_PATH.extend(alias_files) + SPRITE_PATH.extend(atl_files) + + +_init() From 13dcfa3369bedec683a08e89a86e74484d32f531 Mon Sep 17 00:00:00 2001 From: multimokia Date: Mon, 29 Aug 2022 21:29:54 -0400 Subject: [PATCH 089/180] fix this too --- tools/spritepuller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/spritepuller.py b/tools/spritepuller.py index f4821c0dc1..4dce99009b 100644 --- a/tools/spritepuller.py +++ b/tools/spritepuller.py @@ -1,6 +1,6 @@ ## module with a function that pull sprites out of the sprite-chart # -# VER: Python 2.7 +# VER: Python 3.x import os import gamedir as GDIR From 0e066a52679bb02cc8d68ab89b7b046fcb74a52d Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 20:43:05 +0300 Subject: [PATCH 090/180] fix custom formatter --- Monika After Story/game/definitions.rpy | 75 +++++++++++-------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index a71439cdf5..2fde88a2d1 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -102,6 +102,32 @@ python early: Our string formatter that uses more advanced formatting rules compared to the RenPy one """ + @staticmethod + def _getStoreNameForObject(object_name, *scopes): + """ + Returns the name of the store where the given object + was defined or imported to + + IN: + object_name - the name of the object to look for (string) + scopes - the scopes where we look for the object (storemodule.__dict__) + + OUT: + name of the store module where the object was defined + or empty string if we couldn't find it + """ + for scope in scopes: + if object_name in scope: + stores_names_list = [ + store_module_name + for store_module_name, store_module in sys.modules.items() + if store_module and store_module.__dict__ is scope + ] + if stores_names_list: + return stores_names_list[0] + + return "" + def get_field(self, field_name, args, kwargs): """ Originally this method returns objects by references @@ -116,31 +142,6 @@ python early: OUT: tuple of the object and its key """ - def _getStoreNameForObject(object_name, *scopes): - """ - Returns the name of the store where the given object - was defined or imported to - - IN: - object_name - the name of the object to look for (string) - scopes - the scopes where we look for the object (storemodule.__dict__) - - OUT: - name of the store module where the object was defined - or empty string if we couldn't find it - """ - for scope in scopes: - if object_name in scope: - stores_names_list = [ - store_module_name - for store_module_name, store_module in sys.modules.items() - if store_module and store_module.__dict__ is scope - ] - if stores_names_list: - return stores_names_list[0] - - return "" - # if it's a function call, we eval it if "(" in field_name: # split the string into its components @@ -155,10 +156,10 @@ python early: # now we find the store's name to use in eval if isinstance(kwargs, renpy.substitutions.MultipleDict): - scope_store_name = _getStoreNameForObject(first, *kwargs.dicts) + scope_store_name = self._getStoreNameForObject(first, *kwargs.dicts) else: - scope_store_name = _getStoreNameForObject(first, kwargs) + scope_store_name = self._getStoreNameForObject(first, kwargs) # apply formatting if appropriate if scope_store_name: @@ -175,22 +176,12 @@ python early: args ) ) + # RenPy requires to return a tuple of the object and the kwargs + # as the first item + return ((obj, kwargs), first) - # otherwise just get the reference - else: - first, rest = string._string.formatter_field_name_split(field_name) - - obj = self.get_value(first, args, kwargs) - - for is_attr, i in rest: - if is_attr: - obj = getattr(obj, i) - - else: - obj = obj[i] - - #Fixes an internal renpy change to the convert_field method where it requires a tuple in the first position - return (obj, kwargs), first + # Otherwise fallback to what renpy does: just get the reference + return super().get_field(field_name, args, kwargs) # allows us to use a more advanced string formatting renpy.substitutions.formatter = MASFormatter() From b55ed7dd96a247628304dbcb58a80574c9e426f4 Mon Sep 17 00:00:00 2001 From: multimokia Date: Tue, 30 Aug 2022 13:50:26 -0400 Subject: [PATCH 091/180] *.bak --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96e50d9415..53c92e9963 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ #Catch any persist info persistent -.bak +*.bak #No compiled pycode or pycache __pycache__ From eb2b9f03ead7af6fa19a2352b8c414f2c8c66ab5 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 20:53:16 +0300 Subject: [PATCH 092/180] ignore logs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 53c92e9963..f715bb9581 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ log.txt traceback.txt errors.txt firstrun +log #DDLC specific *.chr From b8cb2919db31a87f859bb562a14430f17c4f1ab6 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 21:54:10 +0300 Subject: [PATCH 093/180] WIP: cleanup build options --- Monika After Story/game/options.rpy | 54 ++++++++++++----------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 9d31f40c66..e8fe504cf3 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -124,38 +124,28 @@ init python: ## This is the archive of data for your mod #build.archive(build.name, "all") - #Add the pictures necessary for the scrollable menu - build.classify("game/gui/**",build.name) - - ## These files get put into your data file - build.classify("game/mod_assets/**",build.name) - #build.classify("game/**.rpy",build.name) #Optional line to include plaintext scripts - build.classify("game/*.rpyc",build.name) #Serialized scripts must be included - build.classify("game/dev/*.*",None) #But not the dev folder - build.classify("README.html",build.name) #Included help file for mod installation - build.classify("game/python-packages/**",build.name)#Additional python pacakges - build.classify("CustomIcon**.**",build.name) - - # add lib and renpy - build.classify("lib/**", build.name) - build.classify("renpy/**", build.name) - - build.package(build.directory_name + "Mod",'zip',build.name,description='DDLC Compatible Mod') - - build.classify('**~', None) - build.classify('**.bak', None) - build.classify('**/.**', None) - build.classify('**/#**', None) - build.classify('**/thumbs.db', None) - build.classify('**.rpy', None) - build.classify('**.psd', None) - build.classify('**.sublime-project', None) - build.classify('**.sublime-workspace', None) - build.classify('/music/*.*', None) - build.classify('script-regex.txt', None) - build.classify('/game/10', None) - build.classify('/game/cache/*.*', None) - build.classify('**.rpa',None) + ## These files will be included in the package + # Add mod assets + build.classify("game/mod_assets/**", build.name) + build.classify("game/gui/**", build.name) + # Add scripts in the 'game/' folder + # build.classify("game/*.rpy", build.name) # Optional, includes source + build.classify("game/*.rpyc", build.name) + # Add python packages + build.classify("game/python-packages/**", build.name) + # Add README + build.classify("README.html", build.name) + # Add icons (NOTE: unused) + build.classify("CustomIcon**.**", build.name) + # Add lib and renpy + # build.classify("lib/**", build.name) + # build.classify("renpy/**", build.name) + + build.package(build.directory_name + "Mod", "zip", build.name, description="DDLC Compatible Mod") + + ## These files will be excluded + # Remove everything else + build.classify("**/**", None) ## Files matching documentation patterns are duplicated in a mac app build, ## so they appear in both the app and the zip file. From c8b44fded060cd0287c598c2be67804f153b07e1 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 23:10:57 +0300 Subject: [PATCH 094/180] WIP make proper crossplatform builds --- Monika After Story/game/options.rpy | 34 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index e8fe504cf3..2d4976cea3 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -126,26 +126,34 @@ init python: ## These files will be included in the package # Add mod assets - build.classify("game/mod_assets/**", build.name) - build.classify("game/gui/**", build.name) - # Add scripts in the 'game/' folder - # build.classify("game/*.rpy", build.name) # Optional, includes source - build.classify("game/*.rpyc", build.name) + build.classify("game/mod_assets/**", "all") + build.classify("game/gui/**", "all") + # Add scripts in the game folder + # build.classify("game/*.rpy", "all") # Optional, includes source + build.classify("game/*.rpyc", "all") # Add python packages - build.classify("game/python-packages/**", build.name) + build.classify("game/python-packages/**", "all") # Add README - build.classify("README.html", build.name) + build.classify("README.html", "all") # Add icons (NOTE: unused) - build.classify("CustomIcon**.**", build.name) + build.classify("CustomIcon**.**", "all") # Add lib and renpy - # build.classify("lib/**", build.name) - # build.classify("renpy/**", build.name) + # build.classify("lib/**", "all") + # build.classify("renpy/**", "all") - build.package(build.directory_name + "Mod", "zip", build.name, description="DDLC Compatible Mod") + # build.package(build.directory_name + "Mod", "zip", "all", description="DDLC Compatible Mod") ## These files will be excluded - # Remove everything else - build.classify("**/**", None) + # Remove everything else from the game folder + build.classify("game/**", None) + # build.classify("**.bak", None) + # build.classify("**.rpy", None) + # build.classify("**.rpa", None) + # Remove cache + build.classify("/game/cache/**", None) + build.classify("/game/saves/**", None) + # Remove logs + build.classify("log/**", None) ## Files matching documentation patterns are duplicated in a mac app build, ## so they appear in both the app and the zip file. From aaed3ff523d98e1d4497957f6719f2316cd5aa86 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 23:16:35 +0300 Subject: [PATCH 095/180] WIP update the package we use for builds --- .github/workflows/mas_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mas_check.yml b/.github/workflows/mas_check.yml index 73faadac18..3d78f1adb2 100644 --- a/.github/workflows/mas_check.yml +++ b/.github/workflows/mas_check.yml @@ -78,4 +78,4 @@ jobs: - name: rpy distribute run: | cd renpy - ./renpy.sh launcher distribute "../mas0105/" --package Mod + ./renpy.sh launcher distribute "../mas0105/" --package market From 0205c8b1df64153984b9ee42eaddf05c393fbfcb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 23:57:16 +0300 Subject: [PATCH 096/180] Final cleanup and rename the executables --- Monika After Story/game/options.rpy | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index 2d4976cea3..d670965e64 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -22,6 +22,9 @@ define gui.about = _("") define build.name = "Monika_After_Story" +## Name of the executables, we must keep it DDLC to obey the guidelines +define build.executable_name = "DDLC" + ## Preference defaults ######################################################### ## Controls the default text speed. The default, 0, is infinite, while any other @@ -137,21 +140,15 @@ init python: build.classify("README.html", "all") # Add icons (NOTE: unused) build.classify("CustomIcon**.**", "all") - # Add lib and renpy - # build.classify("lib/**", "all") - # build.classify("renpy/**", "all") # build.package(build.directory_name + "Mod", "zip", "all", description="DDLC Compatible Mod") ## These files will be excluded # Remove everything else from the game folder build.classify("game/**", None) - # build.classify("**.bak", None) - # build.classify("**.rpy", None) - # build.classify("**.rpa", None) # Remove cache - build.classify("/game/cache/**", None) - build.classify("/game/saves/**", None) + # build.classify("game/cache/**", None) + # build.classify("game/saves/**", None) # Remove logs build.classify("log/**", None) From 3e2d7dae680c026d8eb121036911a31651f5c596 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 30 Aug 2022 23:59:23 +0300 Subject: [PATCH 097/180] Update project.json --- Monika After Story/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/project.json b/Monika After Story/project.json index 54dc1b197c..f137c53541 100644 --- a/Monika After Story/project.json +++ b/Monika After Story/project.json @@ -1 +1 @@ -{"build_update": true, "packages": ["DDLC Mod File", "DDLCMod", "", "source", "Mod"], "add_from": true, "force_recompile": true, "renamed_all": true} \ No newline at end of file +{"build_update": true, "packages": ["DDLC Mod File", "DDLCMod", "", "source", "market"], "add_from": false, "force_recompile": true, "renamed_all": true, "android_build": "Release", "renamed_steam": true} \ No newline at end of file From ef11bd5e33411e38ee6725d135b468851a7ce9fb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 00:08:40 +0300 Subject: [PATCH 098/180] ignore dists --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f715bb9581..2d453c9485 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ log #Toolsmenu things zzzz* + +# Build +Monika_After_Story-[0-9]*.[0-9]*.[0-9]*-dists From 33fbcaef56149ad21b5f76f3608adc9e6b6a2edf Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 00:30:56 +0300 Subject: [PATCH 099/180] fix stockfish input --- Monika After Story/game/chess.rpy | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 50bfb031cc..e6cdf0cb51 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -545,7 +545,7 @@ init python in mas_chess: Chess960 rules are basically: 1. One rook must stay on the left side of king, and another one stay on the right side. - Due to this, the king can never be placed on a-file or h-file. + Due to this, the king can never be placed on a-file or h-file. 2. Bishops must stay on different color square. 3. Pawns must stay like the normal chess game. 4. The position of player A's pieces must be the 'reversed version' of player B's. @@ -3329,6 +3329,14 @@ init python: self._button_done ] + def _send_uci_command(self, cmd: str): + """ + Sends a command to stockfish using its input + """ + self.stockfish.stdin.write( + cmd.encode("utf-8") + ) + def __del__(self): self.stockfish.stdin.close() self.stockfish.wait() @@ -3354,9 +3362,9 @@ init python: """ Starts Monika's analysis of the board """ - self.stockfish.stdin.write("position fen {0}\n".format(self.board.fen())) - self.stockfish.stdin.write("go depth {0}\n".format(persistent._mas_chess_difficulty[1])) - self.stockfish.stdin.write("go movetime {0}\n".format(self.MONIKA_WAITTIME)) + self._send_uci_command("position fen {0}\n".format(self.board.fen())) + self._send_uci_command("go depth {0}\n".format(persistent._mas_chess_difficulty[1])) + self._send_uci_command("go movetime {0}\n".format(self.MONIKA_WAITTIME)) def additional_setup(self): """ @@ -3456,9 +3464,9 @@ init python: self.stockfish = open_stockfish(fp) #Set Monika's parameters - self.stockfish.stdin.write("setoption name Skill Level value {0}\n".format(persistent._mas_chess_difficulty[0])) - self.stockfish.stdin.write("setoption name Contempt value {0}\n".format(self.MONIKA_OPTIMISM)) - self.stockfish.stdin.write("setoption name Ponder value False\n") + self._send_uci_command("setoption name Skill Level value {0}\n".format(persistent._mas_chess_difficulty[0])) + self._send_uci_command("setoption name Contempt value {0}\n".format(self.MONIKA_OPTIMISM)) + self._send_uci_command("setoption name Ponder value False\n") #And set up facilities for asynchronous communication self.queue = collections.deque() From 2692a58ac5a89326cdb28d9f706a1daa276e32a0 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 00:44:58 +0300 Subject: [PATCH 100/180] `decode` before using as `str` --- Monika After Story/game/chess.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index e6cdf0cb51..09b735631f 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -3351,7 +3351,7 @@ init python: with self.lock: res = None while self.queue: - line = self.queue.pop() + line = self.queue.pop().decode("utf-8") match = re.match(r"^bestmove (\w+)", line) if match: res = match.group(1) From 951d2d5c90d8584388d28f9f96d57ca03abfc3ca Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:14:32 +0300 Subject: [PATCH 101/180] fix chess - replace float div with int div to fix pieces positions - improve piece repr - make piece `is_white` a dynamic prop - optimise stockfish move polling - fix subprocess buffering output - fix octal number syntax --- Monika After Story/game/chess.rpy | 84 +++++++++++++++++++------------ 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 09b735631f..6be16019d6 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -2003,7 +2003,7 @@ init python: BUTTON_INDICATOR_X = int(BOARD_X_POS + BOARD_WIDTH + BUTTON_INDICATOR_X_SPACING) #Indicator Y position - INDICATOR_Y = int(BOARD_Y_POS + ((BOARD_HEIGHT - INDICATOR_HEIGHT)/ 2)) + INDICATOR_Y = int(BOARD_Y_POS + ((BOARD_HEIGHT - INDICATOR_HEIGHT)// 2)) #Absolute indicator position INDICATOR_POS = (BUTTON_INDICATOR_X, INDICATOR_Y) @@ -2639,8 +2639,8 @@ init python: piece = self.get_piece_at(self.selected_piece[0], self.selected_piece[1]) px, py = renpy.get_mouse_pos() - px -= MASChessDisplayableBase.PIECE_WIDTH / 2 - py -= MASChessDisplayableBase.PIECE_HEIGHT / 2 + px -= MASChessDisplayableBase.PIECE_WIDTH // 2 + py -= MASChessDisplayableBase.PIECE_HEIGHT // 2 piece.render(width, height, st, at, px, py, renderer) #Ask that we be re-rendered ASAP, so we can show the next frame. @@ -2719,8 +2719,8 @@ init python: mx, my = renpy.get_mouse_pos() mx -= MASChessDisplayableBase.BASE_PIECE_X my -= MASChessDisplayableBase.BASE_PIECE_Y - px = mx / MASChessDisplayableBase.PIECE_WIDTH - py = my / MASChessDisplayableBase.PIECE_HEIGHT + px = mx // MASChessDisplayableBase.PIECE_WIDTH + py = my // MASChessDisplayableBase.PIECE_HEIGHT #White if self.is_player_white: @@ -2791,7 +2791,7 @@ init python: OUT: tuple - (x, y) coords representing board coordinates for the square provided """ - return (sq_num % 8, sq_num / 8) + return (sq_num % 8, sq_num // 8) @staticmethod def board_coords_to_screen_coords(pos_tuple, inversion_tuple=(False,False)): @@ -2860,9 +2860,17 @@ init python: for symbol in mas_chess.PIECE_POOL } + NAMES_MAP = { + "b": "Bishop", + "k": "King", + "n": "Knight", + "p": "Pawn", + "r": "Rook", + "q": "Qeeb" + } + def __init__( self, - is_white, symbol, posX, posY, @@ -2872,20 +2880,18 @@ init python: MASPiece constructor IN: - is_white - Whether or not the piece is white symbol - letter symbol representing the piece. If capital, the piece is white posX - x position of the piece posY - y position of the piece piece_map - Map to store this piece in """ - self.is_white = is_white self.symbol = symbol #Store an internal reference to the piece map so we can execute moves from the piece self.piece_map = piece_map #Store the internal reference to this piece's image fp for use in rendering - self.__piece_image = MASPiece.IMG_MAP[MASPiece.FP_COLOR_LOOKUP[is_white] + symbol] + self.__piece_image = MASPiece.IMG_MAP[MASPiece.FP_COLOR_LOOKUP[self.is_white] + symbol] #Internal reference to the position self.x_pos = posX @@ -2906,7 +2912,18 @@ init python: """ Handles a representation of this piece """ - return "MASPiece which: {0} and symbol: {1}".format("is white" if self.is_white else "is black", self.symbol) + return "MASPiece<{0} {1}>".format( + "White" if self.is_white else "Black", + self.name + ) + + @property + def name(self) -> str: + return self.NAMES_MAP[self.symbol.lower()] + + @property + def is_white(self) -> bool: + return self.symbol.isupper() @staticmethod def fromPiece(piece, pos_tuple, piece_map): @@ -2923,7 +2940,6 @@ init python: MASPiece """ return MASPiece( - piece.color, piece.symbol(), pos_tuple[0], pos_tuple[1], @@ -3348,8 +3364,8 @@ init python: OUT: move - representing the best move stockfish found """ + res = None with self.lock: - res = None while self.queue: line = self.queue.pop().decode("utf-8") match = re.match(r"^bestmove (\w+)", line) @@ -3379,14 +3395,21 @@ init python: path - filepath to the stockfish application startupinfo - startup flags """ - try: + def start_stockfish_proc(path: str, startupinfo: subprocess.STARTUPINFO) -> subprocess.Popen: + """ + Tries to launch a stockfish subprocess, can raise exceptions + """ return subprocess.Popen( os.path.join(renpy.config.gamedir, path).replace('\\', '/'), + bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, startupinfo=startupinfo ) + try: + return start_stockfish_proc(path, startupinfo) + #Catch the permission error except OSError as os_err: if not renpy.windows: @@ -3417,12 +3440,7 @@ init python: renpy.hide_screen("mas_py_console_teaching") #Try again try: - stockfish_proc = subprocess.Popen( - os.path.join(renpy.config.gamedir, path).replace('\\', '/'), - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - startupinfo=startupinfo - ) + stockfish_proc = start_stockfish_proc(path, startupinfo) renpy.show("monika 3hua", at_list=[t11]) renpy.say(m, "Yay! We should be able to play now~") @@ -3460,7 +3478,7 @@ init python: elif is_64_bit: fp = "mod_assets/games/chess/stockfish_8_{0}_x64".format("linux" if renpy.linux else "macosx") - os.chmod(config.basedir + "/game/".format(fp), 0755) + os.chmod(config.basedir + "/game/".format(fp), 0o755) self.stockfish = open_stockfish(fp) #Set Monika's parameters @@ -3607,21 +3625,23 @@ init python: # Poll Monika for moves if it's her turn if not self.is_game_over: #Queue a Moni move if this is implemented - monika_move = self.poll_monika_move() + monika_move = None + while monika_move is None: + # We have to wait for stockfish to send us a move + monika_move = self.poll_monika_move() - if monika_move is not None: - #Now verify legality - monika_move_check = chess.Move.from_uci(monika_move) + #Now verify legality + monika_move_check = chess.Move.from_uci(monika_move) - if self.board.is_legal(monika_move_check): - #Monika is thonking - renpy.pause(1.5) + if self.board.is_legal(monika_move_check): + #Monika is thonking + renpy.pause(1.5) - #Push her move - self.__push_move(monika_move) + #Push her move + self.__push_move(monika_move) - #Set the buttons - self.set_button_states() + #Set the buttons + self.set_button_states() def set_button_states(self): """ From 8d6407789e332d72b680704c78d951865f88c386 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:19:19 +0300 Subject: [PATCH 102/180] fix `_MASMoniFollowTransformDissolve` basically just update it to renpy 8 --- Monika After Story/game/sprite-chart.rpy | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index acbc86efeb..c58d1c2215 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -9415,7 +9415,7 @@ python early: """ Adds 4 new params """ - super(_MASMoniFollowTransformDissolve, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # KEY CHANGES self.new_widget_st = None @@ -9447,16 +9447,31 @@ python early: bottom = renpy.display.transition.render(self.old_widget, width, height, old_widget_st, old_widget_at) top = renpy.display.transition.render(self.new_widget, width, height, new_widget_st, new_widget_at) + # END KEY CHANGES width = min(top.width, bottom.width) height = min(top.height, bottom.height) - rv = renpy.display.render.Render(width, height) #opaque=not self.alpha) + rv = renpy.display.render.Render(width, height) rv.operation = renpy.display.render.DISSOLVE - rv.operation_alpha = self.alpha + rv.operation_alpha = self.alpha or renpy.config.dissolve_force_alpha rv.operation_complete = complete + if renpy.display.render.models: + + target = rv.get_size() + + if top.get_size() != target: + top = top.subsurface((0, 0, width, height)) + if bottom.get_size() != target: + bottom = bottom.subsurface((0, 0, width, height)) + + rv.mesh = True + rv.add_shader("renpy.dissolve") + rv.add_uniform("u_renpy_dissolve", complete) + rv.add_property("mipmap", renpy.config.mipmap_dissolves if (self.style.mipmap is None) else self.style.mipmap) + rv.blit(bottom, (0, 0), focus=False, main=False) rv.blit(top, (0, 0), focus=True, main=True) From 599c16b28baac3d098616ecf7d6d071415495550 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:24:53 +0300 Subject: [PATCH 103/180] remove old log code This no longer works and there's no reason to update it for py3 --- Monika After Story/game/0utils.rpy | 128 +---------------------------- 1 file changed, 1 insertion(+), 127 deletions(-) diff --git a/Monika After Story/game/0utils.rpy b/Monika After Story/game/0utils.rpy index fefa692e6d..434e7346e9 100644 --- a/Monika After Story/game/0utils.rpy +++ b/Monika After Story/game/0utils.rpy @@ -406,132 +406,6 @@ python early in mas_utils: # Keep all warnings deprecated.__all_warnings__ = set() - # mac logging - class MASMacLog(renpy.renpy.log.LogFile): - def __init__(self, name, append=False, developer=False, flush=True): - """ - `name` - The name of the logfile, without the .txt extension. - `append` - If true, we will append to the logfile. If false, we will truncate - it to an empty file the first time we write to it. - `developer` - If true, nothing happens if config.developer is not set to True. - `flush` - Determines if the file is flushed after each write. - """ - super(MASMacLog, self).__init__(name, append=append, developer=developer, flush=flush) - - - def open(self): # @ReservedAssignment - if self.file: - return True - - if self.file is False: - return False - - if self.developer and not renpy.config.developer: - return False - - if not renpy.config.log_enable: - return False - - try: - - home = os.path.expanduser("~") - base = os.path.join(home,".MonikaAfterStory/" ) - - if base is None: - return False - - fn = os.path.join(base, self.name + ".txt") - - path, filename = os.path.split(fn) - if not os.path.exists(path): - os.makedirs(path) - - if self.append: - mode = "a" - else: - mode = "w" - - if renpy.config.log_to_stdout: - self.file = real_stdout - - else: - - try: - self.file = codecs.open(fn, mode, "utf-8") - except: - pass - - if self.append: - self.write('') - self.write('=' * 78) - self.write('') - - self.write("%s", time.ctime()) - try: - self.write("%s", platform.platform()) - except: - self.write("Unknown platform.") - self.write("%s", renpy.version()) - self.write("%s %s", renpy.config.name, renpy.config.version) - self.write("") - - return True - - except: - self.file = False - return False - - # A map from the log name to a log object. - mas_mac_log_cache = { } - - @deprecated(use_instead="mas_logging.init_log") - def macLogOpen(name, append=False, developer=False, flush=False): # @ReservedAssignment - rv = mas_mac_log_cache.get(name, None) - - if rv is None: - rv = MASMacLog(name, append=append, developer=developer, flush=flush) - mas_mac_log_cache[name] = rv - - return rv - - @deprecated(use_instead="mas_logging.init_log") - def getMASLog(name, append=False, developer=False, flush=False): - if renpy.macapp or renpy.macintosh: - return macLogOpen(name, append=append, developer=developer, flush=flush) - return renpy.renpy.log.open(name, append=append, developer=developer, flush=flush) - - @deprecated(use_instead="mas_logging.init_log") - def logcreate(filepath, append=False, flush=False, addversion=False): - """ - Creates a log at the given filepath. - This also opens the log and sets raw_write to True. - This also adds per version number if desired - - IN: - filepath - filepath of the log to create (extension is added) - append - True will append to the log. False will overwrite - (Default: False) - flush - True will flush every operation, False will not - (Default: False) - addversion - True will add the version, False will not - You dont need this if you create the log in runtime, - (Default: False) - - RETURNS: created log object. - """ - new_log = getMASLog(filepath, append=append, flush=flush) - new_log.open() - new_log.raw_write = True - if addversion: - new_log.write("VERSION: {0}\n".format( - store.persistent.version_number - )) - return new_log - @deprecated(use_instead="mas_utils.mas_log.info") def writelog(msg): """ @@ -627,7 +501,7 @@ python early in mas_utils: class IsolatedFlexProp(object): """ class that supports flexible attributes. - all attributes that are set are stored in a + all attributes that are set are stored in a separate internal structure. Supports a few additional behaviors because of this. From 4a74692fed9de3b812a27cdeafff833bd843cdcd Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:31:13 +0300 Subject: [PATCH 104/180] unused utils --- Monika After Story/game/0utils.rpy | 61 ------------------------------ 1 file changed, 61 deletions(-) diff --git a/Monika After Story/game/0utils.rpy b/Monika After Story/game/0utils.rpy index 434e7346e9..f2ebb470f2 100644 --- a/Monika After Story/game/0utils.rpy +++ b/Monika After Story/game/0utils.rpy @@ -436,67 +436,6 @@ python early in mas_utils: """ mas_log.debug("".join(traceback.format_stack())) - #"No longer necessary as all logs have builtin rotation" - @deprecated() - def logrotate(logpath, filename): - """ - Does a log rotation. Log rotations contstantly increase. We defualt - to about 2 decimal places, but let the limit go past that - - NOTE: exceptions are logged - - IN: - logpath - path to the folder containing logs - NOTE: this is assumed to have the trailing slash - filename - filename of the log to rotate - """ - try: - filelist = os.listdir(logpath) - except Exception as e: - mas_log.error(str(e)) - return - - # log rotation constants - __numformat = "{:02d}" - __numdelim = "." - - # parse filelist for valid filenames, - # also sort them so the largest number is last - filelist = sorted([ - x - for x in filelist - if x.startswith(filename) - ]) - - # now extract only the largest number in this list. - # NOTE: this is only possible if we have more than one file in the list - if len(filelist) > 1: - fname, dot, largest_num = filelist.pop().rpartition(__numdelim) - largest_num = tryparseint(largest_num, -1) - - else: - # otherwise - largest_num = -1 - - # now increaese largest num to get the next number we should write out - largest_num += 1 - - # delete whatever file that is if it exists - new_path = os.path.normcase("".join([ - logpath, - filename, - __numdelim, - __numformat.format(largest_num) - ])) - trydel(new_path) - - # and copy our main file over - old_path = os.path.normcase(logpath + filename) - copyfile(old_path, new_path) - - # and delete the current file - trydel(old_path) - class IsolatedFlexProp(object): """ From ced3ab43350f76c62f4b62e544b6be514d4c1130 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:34:48 +0300 Subject: [PATCH 105/180] unused code in definitions --- Monika After Story/game/definitions.rpy | 245 +----------------------- 1 file changed, 9 insertions(+), 236 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 2fde88a2d1..e879361632 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -1658,148 +1658,6 @@ python early: return eventlabels - @store.mas_utils.deprecated(should_raise=True) - @staticmethod - def checkConditionals(events, rebuild_ev=False): - # NOTE: DEPRECATED - # - # This checks the conditionals for all of the events in the event list - # if any evaluate to true, run the desired action then clear the - # conditional. - # - # IN: - # rebulid_ev - pass in True to notify idle to rebuild events - # if a random action occured. - import datetime - - # sanity check - if not events or len(events) == 0: - return None - - _now = datetime.datetime.now() - - for ev_label,ev in events.items(): - # TODO: honestly, we should index events with conditionals - # so we only check what needs to be checked. Its a bit of an - # annoyance to check all of these properties once per minute. - - # NOTE: we only check events with: - # - a conditional property - # - current affection is within aff_range - # - has None for date properties - - if ( - # has conditional property - ev.conditional is not None - - # within aff range - and ev.checkAffection(mas_curr_affection) - - # no date props - and ev.start_date is None - and ev.end_date is None - - # check if the action is valid - and ev.action in Event.ACTION_MAP - - # finally check if the conditional is true - and eval(ev.conditional) - ): - - # perform action - Event._performAction( - ev, - unlock_time=_now, - rebuild_ev=rebuild_ev - ) - - #Clear the conditional - ev.conditional = None - - - return events - - @store.mas_utils.deprecated(should_raise=True) - @staticmethod - def checkCalendar(events): - # NOTE: DEPRECATED - # - # This checks the date for all events to see if they are active. - # If they are active, then it checks for a conditional, and evaluates - # if an action should be run. - import datetime - - # sanity check - if not events or len(events) == 0: - return None - - # dict check - ev_list = events.keys() # python 2 - - current_time = datetime.datetime.now() - # insertion sort - for ev in ev_list: - - e = events[ev] - - #If the event has no time-dependence, don't check it - if (e.start_date is None) and (e.end_date is None): - continue - - #Calendar must be based on a date - if e.start_date is not None: - if e.start_date > current_time: - continue - - if e.end_date is not None: - if e.end_date <= current_time: - continue - - if e.conditional is not None: - if not eval(e.conditional): - continue - - - if e.action in Event.ACTION_MAP: - # perform action - Event._performAction(e, unlock_time=current_time) - - # Check if we have a years property - if e.years is not None: - - # if it's an empty list - if len(e.years) == 0: - - # get event ready for next year - e.start_date = store.mas_utils.add_years(e.start_date, 1) - e.end_date = store.mas_utils.add_years(e.end_date, 1) - continue - - # if it's not empty, get all the years that are in the future - new_years = [year for year in e.years if year > e.start_date.year] - - # if we have possible new years - if len(new_years) > 0: - # sort them to ensure we get the nearest one - new_years.sort() - - # pick it - new_year = new_years[0] - - # get the difference - diff = new_year - e.start_date.year - - # update event for the year it should repeat - e.start_date = store.mas_utils.add_years(e.start_date, diff) - e.end_date = store.mas_utils.add_years(e.end_date, diff) - continue - - # Clear the conditional since the event shouldn't repeat - events[ev].conditional = "False" - - return events - - @staticmethod def _checkEvent(ev, curr_time): """ @@ -1848,8 +1706,15 @@ python early: _now = datetime.datetime.now() for ev_label,ev in ev_dict.items(): - # TODO: same TODO as in checkConditionals. - # indexing would be smarter. + # TODO: honestly, we should index events with conditionals + # so we only check what needs to be checked. Its a bit of an + # annoyance to check all of these properties once per minute. + + # NOTE: we only check events with: + # - a conditional property + # - current affection is within aff_range + # - has None for date properties + # indexing would be smarter. if Event._checkEvent(ev, _now): # perform action @@ -1867,87 +1732,6 @@ python early: return - @store.mas_utils.deprecated(should_raise=True) - @staticmethod - def _checkRepeatRule(ev, check_time, defval=True): - """DEPRECATED - - (remove when farewells is updated) - - Checks a single event against its repeat rules, which are evaled - to a time. - NOTE: no sanity checks - TODO: include checkConditional - - IN: - ev - single event to check - check_time - datetime used to check time rules - defval - defval to pass into the rules - (Default: True) - - RETURNS: - True if this event passes its repeat rule, False otherwise - """ - # check if the event contains a MASSelectiveRepeatRule and - # evaluate it - if MASSelectiveRepeatRule.evaluate_rule( - check_time, ev, defval=defval - ): - return True - - # check if the event contains a MASNumericalRepeatRule and - # evaluate it - if MASNumericalRepeatRule.evaluate_rule( - check_time, ev, defval=defval - ): - return True - - return False - - @store.mas_utils.deprecated(should_raise=True) - @staticmethod - def checkRepeatRules(events, check_time=None): - """DEPRECATED - - (remove when farewells is updated) - - checks the event dict against repeat rules, which are evaluated - to a time. - - IN: - events - dict of events of the following format: - eventlabel: event object - check_time - the datetime object that will be used to check the - timed rules, if none is passed we check against the current time - - RETURNS: - A filtered dict containing the events that passed their own rules - for the given check_time - """ - # sanity check - if not events or len(events) == 0: - return None - - # if check_time is none we check against current time - if check_time is None: - check_time = datetime.datetime.now() - - # prepare empty dict to store events that pass their own rules - available_events = dict() - - # iterate over each event in the given events dict - for label, event in events.items(): - if Event._checkRepeatRule(event, check_time, defval=False): - - if event.monikaWantsThisFirst(): - return {event.eventlabel: event} - - available_events[event.eventlabel] = event - - # return the available events dict - return available_events - - @staticmethod def _checkFarewellRule(ev): """ @@ -3063,17 +2847,6 @@ python early: self.corners[-1] )) -# init -1 python: - @store.mas_utils.deprecated(should_raise=True) - class MASInteractable(renpy.Displayable): - """DEPRECATED - - Do not use this. - """ - - def __init__(self, *args, **kwargs): - pass - # init -1 python: # new class to manage a list of quips From 4262a2e0526219031584336f6142138c3acfc157 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:40:40 +0300 Subject: [PATCH 106/180] unused code in ev handler --- Monika After Story/game/event-handler.rpy | 71 ----------------------- 1 file changed, 71 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 484940789c..05c2d14e4b 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -2405,33 +2405,6 @@ init python: # now this event has passsed checks, we can add it to the db eventdb.setdefault(event.eventlabel, event) - @store.mas_utils.deprecated("mas_hideEVL", should_raise=True) - def hideEventLabel( - eventlabel, - lock=False, - derandom=False, - depool=False, - decond=False, - eventdb=evhand.event_database - ): - # - # NOTE: DEPRECATED - # hide an event in the given eventdb by Falsing its unlocked, - # random, and pool properties. - # - # IN: - # eventlabel - label of the event to hide - # lock - True if we want to lock this event, False otherwise - # (Default: False) - # derandom - True if we want to unrandom this event, False otherwise - # (Default: False) - # depool - True if we want to unpool this event, False otherwise - # (Default: False) - # decond - True if we want to remove the conditional, False otherwise - # (Default: False) - # eventdb - the event database (dict) we want to reference - # (DEfault: evhand.event_database) - mas_hideEventLabel(eventlabel, lock, derandom, depool, decond, eventdb) @store.mas_utils.deprecated("mas_hideEvent") def hideEvent( @@ -2581,29 +2554,6 @@ init python: """ mas_showEvent(eventdb.get(ev_label, None), unlock, _random, _pool) - @store.mas_utils.deprecated("mas_lockEvent", should_raise=True) - def lockEvent(ev): - """ - NOTE: DEPRECATED - Locks the given event object - - IN: - ev - the event object to lock - """ - mas_lockEvent(ev) - - @store.mas_utils.deprecated("mas_lockEventLabel", should_raise=True) - def lockEventLabel(evlabel, eventdb=evhand.event_database): - """ - NOTE: DEPRECATED - Locks the given event label - - IN: - evlabel - event label of the event to lock - eventdb - Event database to find this label - """ - mas_lockEventLabel(evlabel, eventdb) - def mas_lockEvent(ev): """ @@ -2666,17 +2616,6 @@ init python: MASEventList.queue(event_label, notify) - @store.mas_utils.deprecated("mas_unlockEvent", should_raise=True) - def unlockEvent(ev): - """ - NOTE: DEPRECATED - Unlocks the given evnet object - - IN: - ev - the event object to unlock - """ - mas_unlockEvent(ev) - @store.mas_utils.deprecated("mas_unlockEventLabel") def unlockEventLabel(evlabel, eventdb=evhand.event_database): """ @@ -2760,16 +2699,6 @@ init python: return evhand._isPresent(ev) - @store.mas_utils.deprecated("MASEventList.pop", should_raise=True) - def popEvent(remove=True): - """ - DO NOT USE. - - Use MASEventList.pop instead (not exactly the same) - """ - pass - - def seen_event(event_label): """ Please use mas_seenEvent, this function hasn't been deprecated From 623e6b47d1f83a70252b214f50482609e1c695ef Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:43:05 +0300 Subject: [PATCH 107/180] add `mas_pushEvent` and `mas_queueEvent` --- Monika After Story/game/event-handler.rpy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 05c2d14e4b..b6608a21bf 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -2576,9 +2576,9 @@ init python: evhand._lockEventLabel(evlabel, eventdb=eventdb) - @store.mas_utils.deprecated("MASEventList.push") - def pushEvent(event_label, skipeval=False, notify=False): + def mas_pushEvent(*args, **kwargs): """ + NOTE: Preferable to use MASEventList.push This pushes high priority or time sensitive events onto the top of the event list @@ -2594,12 +2594,12 @@ init python: ASSUMES: persistent.event_list """ - MASEventList.push(event_label, skipeval, notify) + MASEventList.push(*args, **kwargs) - @store.mas_utils.deprecated("MASEventList.queue") - def queueEvent(event_label, notify=False): + def mas_queueEvent(*args, **kwargs): """ + NOTE: Preferable to use MASEventList.queue This adds low priority or order-sensitive events onto the bottom of the event list. This is slow, but rarely called and list should be small. @@ -2613,7 +2613,7 @@ init python: ASSUMES: persistent.event_list """ - MASEventList.queue(event_label, notify) + MASEventList.queue(*args, **kwargs) @store.mas_utils.deprecated("mas_unlockEventLabel") From 15ef5beae22ae12b5cf0a70239730f9f54cca12e Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:44:45 +0300 Subject: [PATCH 108/180] unused code in ev rules --- Monika After Story/game/event-rules.rpy | 77 ------------------------- 1 file changed, 77 deletions(-) diff --git a/Monika After Story/game/event-rules.rpy b/Monika After Story/game/event-rules.rpy index d4464b07a8..a3ffffb121 100644 --- a/Monika After Story/game/event-rules.rpy +++ b/Monika After Story/game/event-rules.rpy @@ -612,83 +612,6 @@ init -1 python: # Evaluate randint with a chance of 1 in random_chance return renpy.random.randint(1,random_chance) == 1 - @store.mas_utils.deprecated(use_instead="the aff_range property for Events", should_raise=True) - class MASAffectionRule(object): - """ - NOTE: DEPRECATED - Use the aff_range property for Events instead - - Static Class used to create affection specific rules in tuple form. - That tuple is then stored in a dict containing this rule name constant. - Each rule is defined by a min and a max determining a range of affection - to check against. - """ - - @store.mas_utils.deprecated(use_instead="the aff_range property for Events", should_raise=True) - @staticmethod - def create_rule(min, max, ev=None): - """ - IN: - min - An int representing the minimal(inclusive) affection required - for the event to be available, if None is passed is assumed - that there's no minimal affection - max - An int representing the maximum(inclusive) affection required - for the event to be available, if None is passed is assumed - that there's no maximum affection - ev - Event to create rule for, if passed in - (Default: None) - - RETURNS: - a dict containing the specified rules - """ - - # both min and max can't be None at the same time, since that means - # that this is not affection dependent - if not min and not max: - raise Exception("at least min or max must not be None") - - # return the rule inside a dict - rule = {EV_RULE_AFF_RANGE : (min, max)} - - if ev: - ev.rules.update(rule) - - return rule - - @store.mas_utils.deprecated(use_instead="the aff_range property for Events", should_raise=True) - @staticmethod - def evaluate_rule(event=None, rule=None, affection=None, noRuleReturn=False): - """ - IN: - event - the event to evaluate - rule - the MASAffectionRule to check against - affection - the affection to check the rule against - - RETURNS: - True if the current affection is inside the rule range - """ - - # check if we have an event that contains the rule we need - # event rule takes priority so it's checked here - - if event and EV_RULE_AFF_RANGE in event.rules: - rule = event.rules[EV_RULE_AFF_RANGE] - - # sanity check if we don't have a rule return False - if rule is None: - return noRuleReturn - - # store affection for easy checking - if not affection: - affection = persistent._mas_affection["affection"] - - # unpack the rule for easy access - min, max = rule - - # Evaluate if affection is inside the rule range, in case both are None - # will return true (however that case should be catched on create_rule) - return (affection >= min and not max) or (min <= affection <= max) - class MASPriorityRule(object): """ From 636d9a6da6546d4451929af5dcebb0b3d43f7fe2 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:45:10 +0300 Subject: [PATCH 109/180] unused code in progression --- Monika After Story/game/progression.rpy | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Monika After Story/game/progression.rpy b/Monika After Story/game/progression.rpy index 00084e53e6..047d4c5495 100644 --- a/Monika After Story/game/progression.rpy +++ b/Monika After Story/game/progression.rpy @@ -316,20 +316,3 @@ init python in mas_xp: ) if xp_rate < 1: xp_rate = 1.0 - - -init python: - @store.mas_utils.deprecated(should_raise=True) - def grant_xp(experience): - """DEPRECATED - This does not do anything anymore. Around for compatibility - purposes - """ - pass - - @store.mas_utils.deprecated(should_raise=True) - def get_level(): - """DEPRECATED - This does not do anything anymore. Around for compatibility purposes - """ - return 0 From ef35f4267156a1d1157bf6849b39b4ee6710ba7a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:45:59 +0300 Subject: [PATCH 110/180] unused code in ch30 --- Monika After Story/game/script-ch30.rpy | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 1188e4ea37..597eafa9bc 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -521,19 +521,6 @@ init python: # config.keymap['dismiss'] = dismiss_keys # renpy.display.behavior.clear_keymap_cache() - @store.mas_utils.deprecated(use_instead="mas_isDayNow", should_raise=True) - def mas_isMorning(): - """DEPRECATED - Checks if it is day or night via suntimes - - NOTE: the wording of this function is bad. This does not literally - mean that it is morning. USE mas_isDayNow - - RETURNS: True if day, false if not - """ - return mas_isDayNow() - - def mas_progressFilter(): """ Changes filter according to rules. @@ -548,13 +535,6 @@ init python: return curr_flt != new_flt - @store.mas_utils.deprecated(should_raise=True) - def mas_shouldChangeTime(): - """DEPRECATED - This no longer makes sense with the filtering system. - """ - return False - def mas_shouldRain(): """ From 1dd405c7c137cb9c20b02407643299ede0975513 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:46:55 +0300 Subject: [PATCH 111/180] unused code in chart mtx --- Monika After Story/game/sprite-chart-matrix.rpy | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Monika After Story/game/sprite-chart-matrix.rpy b/Monika After Story/game/sprite-chart-matrix.rpy index bb6ed056f9..ba6cbfb714 100644 --- a/Monika After Story/game/sprite-chart-matrix.rpy +++ b/Monika After Story/game/sprite-chart-matrix.rpy @@ -868,12 +868,6 @@ init -99 python in mas_sprites: FILTERS[flt_enum] = imx - @store.mas_utils.deprecated(use_instead="get_filter", should_raise=True) - def _decide_filter(): - """DEPRECATED - Please use get_filter - """ - return get_filter() def get_filter(): From 4fdb87f9f54805923b766813305d4280020aecaf Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:48:10 +0300 Subject: [PATCH 112/180] unused code in sprite charts --- Monika After Story/game/sprite-chart.rpy | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index c58d1c2215..4df896a155 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -1088,27 +1088,6 @@ init -5 python in mas_sprites: return allow_none return _verify_uprightpose(val) or _verify_leaningpose(val) - @store.mas_utils.deprecated(should_raise=True) - def acs_lean_mode(sprite_list, lean): - """ - NOTE: DEPRECATED - - Adds the appropriate accessory prefix dpenedong on lean - - IN: - sprite_list - list to add sprites to - lean - type of lean - """ - if lean: - sprite_list.extend(( - PREFIX_ACS_LEAN, - lean, - ART_DLM - )) - - else: - sprite_list.append(PREFIX_ACS) - def face_lean_mode(lean): """ From bf1b9d62b3813e9cb1e4c4793f6aec18041a7168 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:48:28 +0300 Subject: [PATCH 113/180] unused code in update topics --- Monika After Story/game/updates_topics.rpy | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Monika After Story/game/updates_topics.rpy b/Monika After Story/game/updates_topics.rpy index 641a3bd04d..de76ace008 100644 --- a/Monika After Story/game/updates_topics.rpy +++ b/Monika After Story/game/updates_topics.rpy @@ -29,16 +29,6 @@ init -1 python in mas_db_merging: ) -# preeerything -init -1 python: - @store.mas_utils.deprecated(use_instead="mas_versions.clear", should_raise=True) - def clearUpdateStructs(): - """DEPRECATED - Use mas_versions.clear instead - """ - store.mas_versions.clear() - - init 9 python: store.mas_versions.init() From 66d5facdea8f5fa44e4337d17f793027656453e0 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:49:08 +0300 Subject: [PATCH 114/180] unused code in bg --- Monika After Story/game/zz_backgrounds.rpy | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Monika After Story/game/zz_backgrounds.rpy b/Monika After Story/game/zz_backgrounds.rpy index e5709ea70a..3bf7717928 100644 --- a/Monika After Story/game/zz_backgrounds.rpy +++ b/Monika After Story/game/zz_backgrounds.rpy @@ -2341,15 +2341,6 @@ init -10 python: return self.getRoom(flt) - @store.mas_utils.deprecated(use_instead="getDayRooms", should_raise=True) - def getDayRoom(self, weather=None): - """DEPRECATED - Can't use this anymore since there's no single image that defines - "day" anymore. It's all filter based. - See getDayRooms instead - """ - pass - def getDayRooms(self, weather=None): """ Gets all day images for a weather. @@ -2391,15 +2382,6 @@ init -10 python: return None return m_w_m.get(precip_type) - @store.mas_utils.deprecated(use_instead="getNightRooms", should_raise=True) - def getNightRoom(self, weather=None): - """DEPRECATED - Can't use this anymore since there's no single image that defines - "night" anymore. It's all filter-based - See getNightRooms instead - """ - pass - def getNightRooms(self, weather=None): """ Gets all night images for a weather. From 22cb2ea7b65af104ce994bcf2287040780f3b898 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 02:50:33 +0300 Subject: [PATCH 115/180] unused code in selector and weather --- Monika After Story/game/zz_selector.rpy | 10 ---------- Monika After Story/game/zz_weather.rpy | 15 --------------- 2 files changed, 25 deletions(-) diff --git a/Monika After Story/game/zz_selector.rpy b/Monika After Story/game/zz_selector.rpy index d137bdf8a3..145261c70a 100644 --- a/Monika After Story/game/zz_selector.rpy +++ b/Monika After Story/game/zz_selector.rpy @@ -1666,16 +1666,6 @@ init -10 python in mas_selspr: """ _unlock_item(hair, SELECT_HAIR) - @store.mas_utils.deprecated(use_instead="unlock_prompt", should_raise=True) - def unlock_selector(group): - """DEPRECATED - Use unlock_prompt instead - Unlocks the selector of the given group. - - IN: - group - group to unlock selector topic. - """ - unlock_prompt(group) - def json_sprite_unlock(sp_obj, unlock_label=True): """RUNTIME ONLY diff --git a/Monika After Story/game/zz_weather.rpy b/Monika After Story/game/zz_weather.rpy index 99c68c5986..ed8c93a377 100644 --- a/Monika After Story/game/zz_weather.rpy +++ b/Monika After Story/game/zz_weather.rpy @@ -652,21 +652,6 @@ init -50 python: """ self.unlocked = data_tuple[0] - @store.mas_utils.deprecated(use_instead="get_mask", should_raise=True) - def sp_window(self, day): - """DEPRECATED - Use get_mask instead. - This returns whatever get_mask returns. - """ - return self.get_mask() - - @store.mas_utils.deprecated(should_raise=True) - def isbg_window(self, day, no_frame): - """DEPRECATED - Islands are now separate images. See script-islands-event. - """ - return "" - def toTuple(self): """ Converts this MASWeather object into a tuple From 1d208a9431d0747f4c4720c99ad3df17b3fa0948 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 03:17:08 +0300 Subject: [PATCH 116/180] unDennis our pong code fixes spacebar issues fixes inconsistent spacing and tabs fixes globals --- Monika After Story/game/pong.rpy | 72 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index 626f8fb9d4..1b8cb82138 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -8,33 +8,33 @@ default persistent._mas_pm_ever_let_monika_win_on_purpose = False default persistent._mas_pong_difficulty_change_next_game_date = datetime.date.today() init -5 python: - PONG_DIFFICULTY_CHANGE_ON_WIN = +1 - PONG_DIFFICULTY_CHANGE_ON_LOSS = -1 - PONG_DIFFICULTY_POWERUP = +5 - PONG_DIFFICULTY_POWERDOWN = -5 - PONG_PONG_DIFFICULTY_POWERDOWNBIG = -10 + PONG_DIFFICULTY_CHANGE_ON_WIN = +1 + PONG_DIFFICULTY_CHANGE_ON_LOSS = -1 + PONG_DIFFICULTY_POWERUP = +5 + PONG_DIFFICULTY_POWERDOWN = -5 + PONG_PONG_DIFFICULTY_POWERDOWNBIG = -10 #Triggering the same response twice in a row leads to a different response, not all responses reset this (on purpose) - PONG_MONIKA_RESPONSE_NONE = 0 - PONG_MONIKA_RESPONSE_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 1 - PONG_MONIKA_RESPONSE_SECOND_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 2 - PONG_MONIKA_RESPONSE_WIN_LONG_GAME = 3 - PONG_MONIKA_RESPONSE_WIN_SHORT_GAME = 4 - PONG_MONIKA_RESPONSE_WIN_TRICKSHOT = 5 - PONG_MONIKA_RESPONSE_WIN_EASY_GAME = 6 - PONG_MONIKA_RESPONSE_WIN_MEDIUM_GAME = 7 - PONG_MONIKA_RESPONSE_WIN_HARD_GAME = 8 - PONG_MONIKA_RESPONSE_WIN_EXPERT_GAME = 9 - PONG_MONIKA_RESPONSE_WIN_EXTREME_GAME = 10 - PONG_MONIKA_RESPONSE_LOSE_WITHOUT_HITTING_BALL = 11 - PONG_MONIKA_RESPONSE_LOSE_TRICKSHOT = 12 - PONG_MONIKA_RESPONSE_LOSE_LONG_GAME = 13 - PONG_MONIKA_RESPONSE_LOSE_SHORT_GAME = 14 - PONG_MONIKA_RESPONSE_LOSE_EASY_GAME = 15 - PONG_MONIKA_RESPONSE_LOSE_MEDIUM_GAME = 16 - PONG_MONIKA_RESPONSE_LOSE_HARD_GAME = 17 - PONG_MONIKA_RESPONSE_LOSE_EXPERT_GAME = 18 - PONG_MONIKA_RESPONSE_LOSE_EXTREME_GAME = 19 + PONG_MONIKA_RESPONSE_NONE = 0 + PONG_MONIKA_RESPONSE_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 1 + PONG_MONIKA_RESPONSE_SECOND_WIN_AFTER_PLAYER_WON_MIN_THREE_TIMES = 2 + PONG_MONIKA_RESPONSE_WIN_LONG_GAME = 3 + PONG_MONIKA_RESPONSE_WIN_SHORT_GAME = 4 + PONG_MONIKA_RESPONSE_WIN_TRICKSHOT = 5 + PONG_MONIKA_RESPONSE_WIN_EASY_GAME = 6 + PONG_MONIKA_RESPONSE_WIN_MEDIUM_GAME = 7 + PONG_MONIKA_RESPONSE_WIN_HARD_GAME = 8 + PONG_MONIKA_RESPONSE_WIN_EXPERT_GAME = 9 + PONG_MONIKA_RESPONSE_WIN_EXTREME_GAME = 10 + PONG_MONIKA_RESPONSE_LOSE_WITHOUT_HITTING_BALL = 11 + PONG_MONIKA_RESPONSE_LOSE_TRICKSHOT = 12 + PONG_MONIKA_RESPONSE_LOSE_LONG_GAME = 13 + PONG_MONIKA_RESPONSE_LOSE_SHORT_GAME = 14 + PONG_MONIKA_RESPONSE_LOSE_EASY_GAME = 15 + PONG_MONIKA_RESPONSE_LOSE_MEDIUM_GAME = 16 + PONG_MONIKA_RESPONSE_LOSE_HARD_GAME = 17 + PONG_MONIKA_RESPONSE_LOSE_EXPERT_GAME = 18 + PONG_MONIKA_RESPONSE_LOSE_EXTREME_GAME = 19 pong_monika_last_response_id = PONG_MONIKA_RESPONSE_NONE @@ -250,7 +250,10 @@ init: # Recomputes the position of the ball, handles bounces, and # draws the screen. def render(self, width, height, st, at): - global lose_on_purpose + global lose_on_purpose, win_streak_counter + global loss_streak_counter, instant_loss_streak_counter + global pong_angle_last_shot, ball_paddle_bounces + # The Render object we'll be drawing into. r = renpy.Render(width, height) @@ -279,7 +282,7 @@ init: # Bounces the ball up to one time, either up or down if not self.check_bounce_off_top(): - self.check_bounce_off_bottom() + self.check_bounce_off_bottom() # Handles Monika's targeting and speed. @@ -320,12 +323,6 @@ init: # This draws a paddle, and checks for bounces. def paddle(px, py, hotside, is_computer): - global win_streak_counter - global loss_streak_counter - global instant_loss_streak_counter - global pong_angle_last_shot - global ball_paddle_bounces - # Render the paddle image. We give it an 1280x720 area # to render into, knowing that images will render smaller. # (This isn't the case with all displayables. Solid, Frame, @@ -387,8 +384,13 @@ init: # Draw the ball. ball = renpy.render(self.ball, self.COURT_WIDTH, self.COURT_HEIGHT, st, at) - r.blit(ball, (int(self.bx - self.BALL_WIDTH / 2), - int(self.by - self.BALL_HEIGHT / 2))) + r.blit( + ball, + ( + int(self.bx - self.BALL_WIDTH / 2), + int(self.by - self.BALL_HEIGHT / 2) + ) + ) # Show the player names. player = renpy.render(self.player, self.COURT_WIDTH, self.COURT_HEIGHT, st, at) From 0a6909d58c05e52552203cd0ae7ca9eaa744e109 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 03:30:32 +0300 Subject: [PATCH 117/180] forgot another global --- Monika After Story/game/pong.rpy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Monika After Story/game/pong.rpy b/Monika After Story/game/pong.rpy index 1b8cb82138..a08069c9b3 100644 --- a/Monika After Story/game/pong.rpy +++ b/Monika After Story/game/pong.rpy @@ -323,6 +323,8 @@ init: # This draws a paddle, and checks for bounces. def paddle(px, py, hotside, is_computer): + global ball_paddle_bounces + # Render the paddle image. We give it an 1280x720 area # to render into, knowing that images will render smaller. # (This isn't the case with all displayables. Solid, Frame, From 737357ae877bb8230e2c4fa063df33c59686ec6e Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 04:19:33 +0300 Subject: [PATCH 118/180] backport `ParticleBurst` --- Monika After Story/game/special-effects.rpy | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Monika After Story/game/special-effects.rpy b/Monika After Story/game/special-effects.rpy index bafa648604..fec3ca6c64 100644 --- a/Monika After Story/game/special-effects.rpy +++ b/Monika After Story/game/special-effects.rpy @@ -834,6 +834,53 @@ init -500 python in mas_parallax: return [child for child in self.children] +init -500 python: + import random + import math + + # Backported from DDLC, used in splash screen + class ParticleBurst(object): + def __init__(self, theDisplayable, explodeTime=0, numParticles=20, particleTime = 0.500, particleXSpeed = 3, particleYSpeed = 5): + self.sm = SpriteManager(update=self.update) + + self.stars = [ ] + self.displayable = theDisplayable + self.explodeTime = explodeTime + self.numParticles = numParticles + self.particleTime = particleTime + self.particleXSpeed = particleXSpeed + self.particleYSpeed = particleYSpeed + self.gravity = 240 + self.timePassed = 0 + + for i in range(self.numParticles): + self.add(self.displayable, 1) + + def add(self, d, speed): + s = self.sm.create(d) + speed = random.random() + angle = random.random() * 3.14159 * 2 + xSpeed = speed * math.cos(angle) * self.particleXSpeed + ySpeed = speed * math.sin(angle) * self.particleYSpeed - 1 + s.x = xSpeed * 24 + s.y = ySpeed * 24 + pTime = self.particleTime + self.stars.append((s, ySpeed, xSpeed, pTime)) + + def update(self, st): + sindex=0 + for s, ySpeed, xSpeed, particleTime in self.stars: + if (st < particleTime): + s.x = xSpeed * 120 * (st + .20) + s.y = (ySpeed * 120 * (st + .20) + (self.gravity * st * st)) + else: + s.destroy() + self.stars.pop(sindex) + sindex += 1 + return 0 + + + image yuri dragon2: parallel: "yuri/dragon1.png" From 06fcbea31848a7bbd19c05a32be697f07a059188 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 05:02:50 +0300 Subject: [PATCH 119/180] implement `mas_glitchText` like DDLC's `glitchtext` but better --- Monika After Story/game/definitions.rpy | 48 +++++++++---------- Monika After Story/game/script-affection.rpy | 2 +- .../game/script-easter-eggs.rpy | 4 +- Monika After Story/game/script-farewells.rpy | 6 +-- .../game/script-story-events.rpy | 2 +- Monika After Story/game/zz_hangman.rpy | 2 +- Monika After Story/game/zz_poemgame.rpy | 4 +- 7 files changed, 32 insertions(+), 36 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index bc078d2d52..64fd8b785c 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -357,15 +357,6 @@ python early: f = io.BytesIO(self.data) return renpy.display.pgrender.load_image(f, self.filename) - #Stuff we need from base DDLC - nonunicode = "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž" - - def glitchtext(length): - output = "" - for x in range(length): - output += random.choice(nonunicode) - - return output # uncomment this if you want syntax highlighting support on vim # init -1 python: @@ -2957,7 +2948,7 @@ python early: if self.allow_glitch: # create the glitchtext quip - quip = glitchtext(length) + quip = mas_glitchText(length) # check for cps speed adding if cps_speed > 0 and cps_speed != 1: @@ -3810,21 +3801,6 @@ init -1 python in _mas_root: import datetime import collections - # redefine this because I can't get access to global functions, also - # i dont care to find out how - nonunicode = ( - "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝ" + - "Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘę" + - "ĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖ" + - "ŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž" - ) - - def glitchtext(length): - import random - output = "" - for x in range(length): - output += random.choice(nonunicode) - return output def mangleFile(filepath, mangle_length=1000): """ @@ -3837,7 +3813,7 @@ init -1 python in _mas_root: (Default: 1000) """ import struct - bad_text = glitchtext(mangle_length) + bad_text = store.mas_glitchText(mangle_length) bad_text = [ord(x) for x in bad_text] bad_text = struct.pack("{0}i".format(mangle_length), *bad_text) with open(filepath, "wb") as m_file: @@ -6515,6 +6491,26 @@ init 21 python: renpy.show("monika " + exp_code) return "" + __NONUNICODE = ( + "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝ" + "Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘę" + "ĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖ" + "ŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž" + ) + + def mas_glitchText(length: int) -> str: + """ + Backported and impoved glitchtext from DDLC + + IN: + length - len of the text to generate + + OUT: + str of the generated glitch text + """ + output = [random.choice(__NONUNICODE) for i in range(length)] + return "".join(output) + # Music init -1: define audio.t1 = "bgm/1.ogg" #Main theme (title) diff --git a/Monika After Story/game/script-affection.rpy b/Monika After Story/game/script-affection.rpy index cad18dcc9b..4596bcce19 100644 --- a/Monika After Story/game/script-affection.rpy +++ b/Monika After Story/game/script-affection.rpy @@ -2548,7 +2548,7 @@ label mas_finalfarewell_start: allow_dialogue = False store.songs.enabled = False mas_in_finalfarewell_mode = True - layout.QUIT = glitchtext(20) + layout.QUIT = mas_glitchText(20) #Console is not going to save you. config.keymap["console"] = [] diff --git a/Monika After Story/game/script-easter-eggs.rpy b/Monika After Story/game/script-easter-eggs.rpy index 1f1b5b21b5..6f8d48f9de 100644 --- a/Monika After Story/game/script-easter-eggs.rpy +++ b/Monika After Story/game/script-easter-eggs.rpy @@ -196,10 +196,10 @@ label natsuki_name_scare_hungry: #play special music and display glitch text. $ adjusted_6g = "bgm/6g.ogg" $ renpy.play(adjusted_6g, channel="sound") - $ ntext = glitchtext(96) + $ ntext = mas_glitchText(96) $ style.say_dialogue = style.edited n "{cps=*2}{color=#000}[ntext]{/color}{/cps}{nw}" - $ ntext = glitchtext(96) + $ ntext = mas_glitchText(96) n "{cps=*2}{color=#000}[ntext]{/color}{/cps}{nw}" # tear screen and glitch sound to mark end of glitch. diff --git a/Monika After Story/game/script-farewells.rpy b/Monika After Story/game/script-farewells.rpy index 3f21b107f4..b4cd67762a 100644 --- a/Monika After Story/game/script-farewells.rpy +++ b/Monika After Story/game/script-farewells.rpy @@ -694,10 +694,10 @@ label bye_prompt_sleep: # show screen mas_background_timed_jump(4, "bye_prompt_sleep.reglitch") # $ _history_list.pop() # menu: - # m "[glitchtext(41)]{fast}" - # "[glitchtext(15)]": + # m "[mas_glitchText(41)]{fast}" + # "[mas_glitchText(15)]": # pass - # "[glitchtext(12)]": + # "[mas_glitchText(12)]": # pass # hide screen mas_background_timed_jump diff --git a/Monika After Story/game/script-story-events.rpy b/Monika After Story/game/script-story-events.rpy index 8b5baefc43..01d2b346ca 100644 --- a/Monika After Story/game/script-story-events.rpy +++ b/Monika After Story/game/script-story-events.rpy @@ -1632,7 +1632,7 @@ label mas_corrupted_persistent: call mas_showpoem(mas_note_backups_all_good) window auto - $ _gtext = glitchtext(15) + $ _gtext = mas_glitchText(15) m 1ekc "Do you know what this is about?{nw}" $ _history_list.pop() diff --git a/Monika After Story/game/zz_hangman.rpy b/Monika After Story/game/zz_hangman.rpy index 6e9268ced6..342a129a46 100644 --- a/Monika After Story/game/zz_hangman.rpy +++ b/Monika After Story/game/zz_hangman.rpy @@ -537,7 +537,7 @@ label mas_hangman_game_loop: $ mas_RaiseShield_core() # setup glitch text - $ hm_glitch_word = glitchtext(40) + "?" + $ hm_glitch_word = mas_glitchText(40) + "?" $ style.say_dialogue = style.edited # show hanging sayori diff --git a/Monika After Story/game/zz_poemgame.rpy b/Monika After Story/game/zz_poemgame.rpy index 9fb691ddcd..5ee09e5e4f 100644 --- a/Monika After Story/game/zz_poemgame.rpy +++ b/Monika After Story/game/zz_poemgame.rpy @@ -1248,7 +1248,7 @@ label mas_poem_minigame (flow,music_filename=audio.t4,show_monika=True, random.randint(1,glitch_wordscare[1]) == 1 )): - word = MASPoemWord(glitchtext(7), 0, 0, 0, 0, True) + word = MASPoemWord(mas_glitchText(7), 0, 0, 0, 0, True) # are we displaying a glitched Monika word elif glitch_words: @@ -1277,7 +1277,7 @@ label mas_poem_minigame (flow,music_filename=audio.t4,show_monika=True, random.randint(1,glitch_wordscare[1]) == 1 )): - word.word = glitchtext(len(word.word)) + word.word = mas_glitchText(len(word.word)) word.glitch = True # glitchy words (visual) From a44df299abdd9e9dbc4a3359db425d93828fe6a6 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 05:35:23 +0300 Subject: [PATCH 120/180] Revert "Fix EoF while parsing" This reverts commit 703af7bb140ae13263ffe484cf84de92621bbf9f. This has been fixed in RenPy --- Monika After Story/game/screens.rpy | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 7832b772a4..73def61c53 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -2947,7 +2947,14 @@ screen mas_gen_scrollable_menu(items, display_area, scroll_align, *args): # # OUT: # dict of buttons keys and new values -screen mas_check_scrollable_menu(items, display_area, scroll_align, selected_button_prompt="Done", default_button_prompt="Nevermind", return_all=False): +screen mas_check_scrollable_menu( + items, + display_area, + scroll_align, + selected_button_prompt="Done", + default_button_prompt="Nevermind", + return_all=False +): default buttons_data = { _tuple[1]: { "return_value": _tuple[3] if _tuple[2] else _tuple[4], From 20e04b1467998bcf87ac2781c0fd67fba140c958 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 05:45:23 +0300 Subject: [PATCH 121/180] submods framework fixes --- Monika After Story/game/zz_submods.rpy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Monika After Story/game/zz_submods.rpy b/Monika After Story/game/zz_submods.rpy index eb19bf69cf..7ef64048a6 100644 --- a/Monika After Story/game/zz_submods.rpy +++ b/Monika After Story/game/zz_submods.rpy @@ -119,8 +119,8 @@ init -991 python in mas_submod_utils: #Now we verify that the version number is something proper try: - map(int, version.split('.')) - except: + tuple(map(int, version.split('.'))) + except ValueError: raise SubmodError("Version number '{0}' is invalid.".format(version)) #Make sure author and name are proper label names @@ -159,7 +159,7 @@ init -991 python in mas_submod_utils: OUT: List of integers representing the version number """ - return tuple(map(int, self.version.split('.'))) + return list(map(int, self.version.split('.'))) def hasUpdated(self): """ @@ -177,7 +177,7 @@ init -991 python in mas_submod_utils: return False try: - old_vers = tuple(map(int, old_vers.split('.'))) + old_vers = list(map(int, old_vers.split('.'))) #Persist data was bad, we'll replace it with something safe and return False as we need not check more except: From be83f5815fab45f702bc5dcce7dbcd83a9ed3eb4 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 05:49:17 +0300 Subject: [PATCH 122/180] wrap `filter` in a `list` --- Monika After Story/game/event-handler.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 4f51df2b2b..19ce7cf50e 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -3636,7 +3636,7 @@ label mas_bookmarks_unbookmark(bookmarks_items): persistent._mas_player_bookmarked.remove(ev_label) # filter the removed items to show the menu again - bookmarks_items = filter(lambda item: item[1] not in bookmarks_to_remove, bookmarks_items) + bookmarks_items = list(filter(lambda item: item[1] not in bookmarks_to_remove, bookmarks_items)) show monika at t11 m 1dsa "Okay, [player].{w=0.2}.{w=0.2}.{w=0.2}{nw}" From 12f68196440832f0c94652b1c85da7f22324554b Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 05:55:13 +0300 Subject: [PATCH 123/180] remove `.iter*()` --- Monika After Story/game/sprite-chart.rpy | 2 +- Monika After Story/game/zz_cardgames.rpy | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index 4df896a155..8fabeb6345 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -1354,7 +1354,7 @@ init -5 python in mas_sprites: if predicate: return [ spr_object - for spr_object in sprite_map.itervalues() + for spr_object in sprite_map.values() if predicate(spr_object) ] diff --git a/Monika After Story/game/zz_cardgames.rpy b/Monika After Story/game/zz_cardgames.rpy index 201a382382..3710036c56 100644 --- a/Monika After Story/game/zz_cardgames.rpy +++ b/Monika After Story/game/zz_cardgames.rpy @@ -2401,7 +2401,7 @@ init 5 python in mas_nou: return rv sorted_list = sorted( - cards_data.iteritems(), + cards_data.items(), key=lambda item: sortKey( item, keys_sort_order=keys_sort_order, @@ -5222,7 +5222,7 @@ init -10 python in mas_cardgames: # Fill the map with the sprites (or use the def as a fallback) fb = sprites_map.get(store.mas_background.MBG_DEF) - for bg_id in store.mas_background.BACKGROUND_MAP.iterkeys(): + for bg_id in store.mas_background.BACKGROUND_MAP.keys(): if bg_id not in DESK_SPRITES_MAP: filename = sprites_map.get(bg_id, fb) DESK_SPRITES_MAP[bg_id] = MASFilterSwitch(DESK_SPRITES_PATH + filename) @@ -5450,7 +5450,7 @@ init -10 python in mas_cardgames: layer - the layer we'll render our table on (Default: "minigames") """ - for v in self.cards.itervalues(): + for v in self.cards.values(): v._offset = __Fixed(0, 0) ui.layer(layer) @@ -5659,8 +5659,8 @@ init -10 python in mas_cardgames: Returns a list of all displayable objects we use """ stacks_bases = [stack.base for stack in self.stacks] - cards_faces = [card.face for card in self.cards.itervalues()] - cards_backs = [card.back for card in self.cards.itervalues()] + cards_faces = [card.face for card in self.cards.values()] + cards_backs = [card.back for card in self.cards.values()] return stacks_bases + cards_faces + cards_backs From ca1c1f719097f1e6a89c27e2e6fd47e9976fc669 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 06:02:45 +0300 Subject: [PATCH 124/180] update `global` statements --- Monika After Story/game/definitions.rpy | 5 +++-- Monika After Story/game/screens.rpy | 4 ++-- Monika After Story/game/script-affection.rpy | 2 ++ Monika After Story/game/styles.rpy | 4 +++- Monika After Story/game/zz_cardgames.rpy | 1 + Monika After Story/game/zz_hotkeys.rpy | 3 ++- Monika After Story/game/zz_music_selector.rpy | 4 ++-- Monika After Story/game/zz_weather.rpy | 4 +++- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 64fd8b785c..698cd2ed3f 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -7922,12 +7922,13 @@ init -1 python in mas_randchat: slider_value - slider value given from the slider Should be between 0 - 6 """ + global rand_low + global rand_high + slider_setting = SLIDER_MAP.get(slider_value, 4) # otherwise set up the times # globalize - global rand_low - global rand_high rand_low = slider_setting rand_high = slider_setting * SPAN_MULTIPLIER diff --git a/Monika After Story/game/screens.rpy b/Monika After Story/game/screens.rpy index 73def61c53..59483eb813 100644 --- a/Monika After Story/game/screens.rpy +++ b/Monika After Story/game/screens.rpy @@ -723,7 +723,7 @@ style quick_button_text_dark: ## to other menus, and to start the game. init 4 python: - def FinishEnterName(): + def _finishEnterName(): global player if not player: @@ -855,7 +855,7 @@ screen navigation(): if main_menu: - textbutton _("Just Monika") action If(persistent.playername, true=Start(), false=Show(screen="name_input", message="Please enter your name", ok_action=Function(FinishEnterName))) + textbutton _("Just Monika") action If(persistent.playername, true=Start(), false=Show(screen="name_input", message="Please enter your name", ok_action=Function(_finishEnterName))) else: diff --git a/Monika After Story/game/script-affection.rpy b/Monika After Story/game/script-affection.rpy index 4596bcce19..db28a98f3f 100644 --- a/Monika After Story/game/script-affection.rpy +++ b/Monika After Story/game/script-affection.rpy @@ -917,6 +917,7 @@ init 15 python in mas_affection: Initializes the talk quiplists """ global talk_menu_quips + def save_quips(_aff, quiplist): mas_ql = store.MASQuipList(allow_label=False) for _quip in quiplist: @@ -1054,6 +1055,7 @@ init 15 python in mas_affection: Initializes the play quipliust """ global play_menu_quips + def save_quips(_aff, quiplist): mas_ql = store.MASQuipList(allow_label=False) for _quip in quiplist: diff --git a/Monika After Story/game/styles.rpy b/Monika After Story/game/styles.rpy index 46c7210639..f03d5cc606 100644 --- a/Monika After Story/game/styles.rpy +++ b/Monika After Story/game/styles.rpy @@ -244,6 +244,8 @@ init python in mas_settings: """ Handles the toggling of fields so the menu options become mutually exclusive """ + global dark_mode_clicked + if _persistent._mas_dark_mode_enabled: _persistent._mas_dark_mode_enabled = False @@ -251,7 +253,6 @@ init python in mas_settings: _persistent._mas_dark_mode_enabled = True _persistent._mas_auto_mode_enabled = False - global dark_mode_clicked dark_mode_clicked = True def _ui_change_wrapper(*args): @@ -262,6 +263,7 @@ init python in mas_settings: *args - values to pass to dark mode """ global ui_changed + ui_changed = True store.mas_darkMode(*args) diff --git a/Monika After Story/game/zz_cardgames.rpy b/Monika After Story/game/zz_cardgames.rpy index 3710036c56..bf684e8e46 100644 --- a/Monika After Story/game/zz_cardgames.rpy +++ b/Monika After Story/game/zz_cardgames.rpy @@ -1090,6 +1090,7 @@ init 5 python in mas_nou: player - the player we check """ global winner + if player.hand: return diff --git a/Monika After Story/game/zz_hotkeys.rpy b/Monika After Story/game/zz_hotkeys.rpy index 6f0d2e6745..fa00e5319b 100644 --- a/Monika After Story/game/zz_hotkeys.rpy +++ b/Monika After Story/game/zz_hotkeys.rpy @@ -25,8 +25,9 @@ init -10 python in mas_hotkeys: IN: value - True will allow dismiss, False will not """ + global allow_dismiss + if not lock_dismiss: - global allow_dismiss allow_dismiss = value diff --git a/Monika After Story/game/zz_music_selector.rpy b/Monika After Story/game/zz_music_selector.rpy index 09be225849..4a5317c8bf 100644 --- a/Monika After Story/game/zz_music_selector.rpy +++ b/Monika After Story/game/zz_music_selector.rpy @@ -160,8 +160,8 @@ init -1 python in songs: # sayori - True if the player name is sayori, which means only # allow Surprise in the player - global music_choices - global music_pages + global music_choices, music_pages + music_choices = list() # SONGS: # if you want to add a song, add it to this list as a tuple, where: diff --git a/Monika After Story/game/zz_weather.rpy b/Monika After Story/game/zz_weather.rpy index 5d6c20e4ce..9d72dc0c79 100644 --- a/Monika After Story/game/zz_weather.rpy +++ b/Monika After Story/game/zz_weather.rpy @@ -231,6 +231,7 @@ init -99 python in mas_weather: RETURNS: the created image tag """ global old_weather_id + tag = old_weather_tag.format(old_weather_id) store.renpy.image(tag, disp) OLD_WEATHER_OBJ[old_weather_id] = tag @@ -259,13 +260,13 @@ init -20 python in mas_weather: RETURNS: - True or false on whether or not to call spaceroom """ + global weather_change_time #If the player forced weather or we're not in a background that supports weather, we do nothing if force_weather or store.mas_current_background.disable_progressive: return False #Otherwise we do stuff - global weather_change_time #Set a time for startup if not weather_change_time: # TODO: make this a function so init can set the weather_change _time and prevent double weather setting @@ -989,6 +990,7 @@ init 799 python: _weather - weather to set to. """ global mas_current_weather + old_weather = mas_current_weather mas_current_weather = _weather mas_current_weather.entry(old_weather) From 8809121920c87773d10ba2683c1474ece8b42134 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 06:04:37 +0300 Subject: [PATCH 125/180] remove `mas_set_gender` --- Monika After Story/game/definitions.rpy | 6 ------ Monika After Story/game/script-ch30.rpy | 4 ++-- Monika After Story/game/script-story-events.rpy | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 698cd2ed3f..ad9fde1825 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -8003,12 +8003,6 @@ init 4 python: import store.mas_randchat as mas_randchat -# Deprecated, call mas_set_pronouns directly -label mas_set_gender: - $ mas_set_pronouns() - return - - style jpn_text: font "mod_assets/font/mplus-2p-regular.ttf" diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 51838b1dc9..d638d4fe5c 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -1123,8 +1123,8 @@ label ch30_autoload: mas_cleanEventList() - # set the gender - call mas_set_gender + # set the gender + mas_set_pronouns() # call reset stuff call ch30_reset diff --git a/Monika After Story/game/script-story-events.rpy b/Monika After Story/game/script-story-events.rpy index 01d2b346ca..8e9b22a4ea 100644 --- a/Monika After Story/game/script-story-events.rpy +++ b/Monika After Story/game/script-story-events.rpy @@ -60,7 +60,7 @@ label mas_gender: #Unlock the gender redo event $ mas_unlockEVL("monika_gender_redo","EVE") # set pronouns - call mas_set_gender + $ mas_set_pronouns() #Set up the preferredname topic python: @@ -161,7 +161,7 @@ label monika_gender_redo: m 5hubsa "I'll always love you for who you are~" # set pronouns - call mas_set_gender + $ mas_set_pronouns() return "love" label mas_gender_neither: From c9cc023402e57e9c3e3094b55595d31ea192ec0a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 31 Aug 2022 19:00:26 +0300 Subject: [PATCH 126/180] add `unittests` thank me later --- .../game/python-packages/unittest/__init__.py | 95 + .../game/python-packages/unittest/__main__.py | 18 + .../game/python-packages/unittest/_log.py | 69 + .../python-packages/unittest/async_case.py | 160 + .../game/python-packages/unittest/case.py | 1437 ++++++++ .../game/python-packages/unittest/loader.py | 517 +++ .../game/python-packages/unittest/main.py | 275 ++ .../game/python-packages/unittest/mock.py | 2891 +++++++++++++++++ .../game/python-packages/unittest/result.py | 216 ++ .../game/python-packages/unittest/runner.py | 221 ++ .../game/python-packages/unittest/signals.py | 71 + .../game/python-packages/unittest/suite.py | 361 ++ .../python-packages/unittest/test/__init__.py | 22 + .../python-packages/unittest/test/__main__.py | 18 + .../unittest/test/_test_warnings.py | 73 + .../python-packages/unittest/test/dummy.py | 1 + .../python-packages/unittest/test/support.py | 138 + .../unittest/test/test_assertions.py | 413 +++ .../unittest/test/test_async_case.py | 222 ++ .../unittest/test/test_break.py | 280 ++ .../unittest/test/test_case.py | 1852 +++++++++++ .../unittest/test/test_discovery.py | 879 +++++ .../unittest/test/test_functiontestcase.py | 148 + .../unittest/test/test_loader.py | 1595 +++++++++ .../unittest/test/test_program.py | 440 +++ .../unittest/test/test_result.py | 704 ++++ .../unittest/test/test_runner.py | 1013 ++++++ .../unittest/test/test_setups.py | 507 +++ .../unittest/test/test_skipping.py | 271 ++ .../unittest/test/test_suite.py | 447 +++ .../unittest/test/testmock/__init__.py | 17 + .../unittest/test/testmock/__main__.py | 18 + .../unittest/test/testmock/support.py | 16 + .../unittest/test/testmock/testasync.py | 1059 ++++++ .../unittest/test/testmock/testcallable.py | 150 + .../unittest/test/testmock/testhelpers.py | 1127 +++++++ .../test/testmock/testmagicmethods.py | 509 +++ .../unittest/test/testmock/testmock.py | 2171 +++++++++++++ .../unittest/test/testmock/testpatch.py | 1947 +++++++++++ .../unittest/test/testmock/testsealable.py | 176 + .../unittest/test/testmock/testsentinel.py | 41 + .../unittest/test/testmock/testwith.py | 347 ++ .../game/python-packages/unittest/util.py | 170 + 43 files changed, 23102 insertions(+) create mode 100644 Monika After Story/game/python-packages/unittest/__init__.py create mode 100644 Monika After Story/game/python-packages/unittest/__main__.py create mode 100644 Monika After Story/game/python-packages/unittest/_log.py create mode 100644 Monika After Story/game/python-packages/unittest/async_case.py create mode 100644 Monika After Story/game/python-packages/unittest/case.py create mode 100644 Monika After Story/game/python-packages/unittest/loader.py create mode 100644 Monika After Story/game/python-packages/unittest/main.py create mode 100644 Monika After Story/game/python-packages/unittest/mock.py create mode 100644 Monika After Story/game/python-packages/unittest/result.py create mode 100644 Monika After Story/game/python-packages/unittest/runner.py create mode 100644 Monika After Story/game/python-packages/unittest/signals.py create mode 100644 Monika After Story/game/python-packages/unittest/suite.py create mode 100644 Monika After Story/game/python-packages/unittest/test/__init__.py create mode 100644 Monika After Story/game/python-packages/unittest/test/__main__.py create mode 100644 Monika After Story/game/python-packages/unittest/test/_test_warnings.py create mode 100644 Monika After Story/game/python-packages/unittest/test/dummy.py create mode 100644 Monika After Story/game/python-packages/unittest/test/support.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_assertions.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_async_case.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_break.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_case.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_discovery.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_loader.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_program.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_result.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_runner.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_setups.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_skipping.py create mode 100644 Monika After Story/game/python-packages/unittest/test/test_suite.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/__init__.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/__main__.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/support.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testasync.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testmock.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py create mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testwith.py create mode 100644 Monika After Story/game/python-packages/unittest/util.py diff --git a/Monika After Story/game/python-packages/unittest/__init__.py b/Monika After Story/game/python-packages/unittest/__init__.py new file mode 100644 index 0000000000..348dc471f4 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/__init__.py @@ -0,0 +1,95 @@ +""" +Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's +Smalltalk testing framework (used with permission). + +This module contains the core framework classes that form the basis of +specific test cases and suites (TestCase, TestSuite etc.), and also a +text-based utility class for running the tests and reporting the results + (TextTestRunner). + +Simple usage: + + import unittest + + class IntegerArithmeticTestCase(unittest.TestCase): + def testAdd(self): # test method names begin with 'test' + self.assertEqual((1 + 2), 3) + self.assertEqual(0 + 1, 1) + def testMultiply(self): + self.assertEqual((0 * 10), 0) + self.assertEqual((5 * 8), 40) + + if __name__ == '__main__': + unittest.main() + +Further information is available in the bundled documentation, and from + + http://docs.python.org/library/unittest.html + +Copyright (c) 1999-2003 Steve Purcell +Copyright (c) 2003-2010 Python Software Foundation +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__all__ = ['TestResult', 'TestCase', 'IsolatedAsyncioTestCase', 'TestSuite', + 'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main', + 'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless', + 'expectedFailure', 'TextTestResult', 'installHandler', + 'registerResult', 'removeResult', 'removeHandler', + 'addModuleCleanup'] + +# Expose obsolete functions for backwards compatibility +__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) + +__unittest = True + +from .result import TestResult +from .case import (addModuleCleanup, TestCase, FunctionTestCase, SkipTest, skip, + skipIf, skipUnless, expectedFailure) +from .suite import BaseTestSuite, TestSuite +from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames, + findTestCases) +from .main import TestProgram, main +from .runner import TextTestRunner, TextTestResult +from .signals import installHandler, registerResult, removeResult, removeHandler +# IsolatedAsyncioTestCase will be imported lazily. + +# deprecated +_TextTestResult = TextTestResult + +# There are no tests here, so don't try to run anything discovered from +# introspecting the symbols (e.g. FunctionTestCase). Instead, all our +# tests come from within unittest.test. +def load_tests(loader, tests, pattern): + import os.path + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + return loader.discover(start_dir=this_dir, pattern=pattern) + + +# Lazy import of IsolatedAsyncioTestCase from .async_case +# It imports asyncio, which is relatively heavy, but most tests +# do not need it. + +def __dir__(): + return globals().keys() | {'IsolatedAsyncioTestCase'} + +def __getattr__(name): + if name == 'IsolatedAsyncioTestCase': + global IsolatedAsyncioTestCase + from .async_case import IsolatedAsyncioTestCase + return IsolatedAsyncioTestCase + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Monika After Story/game/python-packages/unittest/__main__.py b/Monika After Story/game/python-packages/unittest/__main__.py new file mode 100644 index 0000000000..e5876f569b --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/__main__.py @@ -0,0 +1,18 @@ +"""Main entry point""" + +import sys +if sys.argv[0].endswith("__main__.py"): + import os.path + # We change sys.argv[0] to make help message more useful + # use executable without path, unquoted + # (it's just a hint anyway) + # (if you have spaces in your executable you get what you deserve!) + executable = os.path.basename(sys.executable) + sys.argv[0] = executable + " -m unittest" + del os + +__unittest = True + +from .main import main + +main(module=None) diff --git a/Monika After Story/game/python-packages/unittest/_log.py b/Monika After Story/game/python-packages/unittest/_log.py new file mode 100644 index 0000000000..94e7e758bd --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/_log.py @@ -0,0 +1,69 @@ +import logging +import collections + +from .case import _BaseTestCaseContext + + +_LoggingWatcher = collections.namedtuple("_LoggingWatcher", + ["records", "output"]) + +class _CapturingHandler(logging.Handler): + """ + A logging handler capturing all (raw and formatted) logging output. + """ + + def __init__(self): + logging.Handler.__init__(self) + self.watcher = _LoggingWatcher([], []) + + def flush(self): + pass + + def emit(self, record): + self.watcher.records.append(record) + msg = self.format(record) + self.watcher.output.append(msg) + + +class _AssertLogsContext(_BaseTestCaseContext): + """A context manager used to implement TestCase.assertLogs().""" + + LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" + + def __init__(self, test_case, logger_name, level): + _BaseTestCaseContext.__init__(self, test_case) + self.logger_name = logger_name + if level: + self.level = logging._nameToLevel.get(level, level) + else: + self.level = logging.INFO + self.msg = None + + def __enter__(self): + if isinstance(self.logger_name, logging.Logger): + logger = self.logger = self.logger_name + else: + logger = self.logger = logging.getLogger(self.logger_name) + formatter = logging.Formatter(self.LOGGING_FORMAT) + handler = _CapturingHandler() + handler.setFormatter(formatter) + self.watcher = handler.watcher + self.old_handlers = logger.handlers[:] + self.old_level = logger.level + self.old_propagate = logger.propagate + logger.handlers = [handler] + logger.setLevel(self.level) + logger.propagate = False + return handler.watcher + + def __exit__(self, exc_type, exc_value, tb): + self.logger.handlers = self.old_handlers + self.logger.propagate = self.old_propagate + self.logger.setLevel(self.old_level) + if exc_type is not None: + # let unexpected exceptions pass through + return False + if len(self.watcher.records) == 0: + self._raiseFailure( + "no logs of level {} or higher triggered on {}" + .format(logging.getLevelName(self.level), self.logger.name)) diff --git a/Monika After Story/game/python-packages/unittest/async_case.py b/Monika After Story/game/python-packages/unittest/async_case.py new file mode 100644 index 0000000000..520213c372 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/async_case.py @@ -0,0 +1,160 @@ +import asyncio +import inspect + +from .case import TestCase + + + +class IsolatedAsyncioTestCase(TestCase): + # Names intentionally have a long prefix + # to reduce a chance of clashing with user-defined attributes + # from inherited test case + # + # The class doesn't call loop.run_until_complete(self.setUp()) and family + # but uses a different approach: + # 1. create a long-running task that reads self.setUp() + # awaitable from queue along with a future + # 2. await the awaitable object passing in and set the result + # into the future object + # 3. Outer code puts the awaitable and the future object into a queue + # with waiting for the future + # The trick is necessary because every run_until_complete() call + # creates a new task with embedded ContextVar context. + # To share contextvars between setUp(), test and tearDown() we need to execute + # them inside the same task. + + # Note: the test case modifies event loop policy if the policy was not instantiated + # yet. + # asyncio.get_event_loop_policy() creates a default policy on demand but never + # returns None + # I believe this is not an issue in user level tests but python itself for testing + # should reset a policy in every test module + # by calling asyncio.set_event_loop_policy(None) in tearDownModule() + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + self._asyncioTestLoop = None + self._asyncioCallsQueue = None + + async def asyncSetUp(self): + pass + + async def asyncTearDown(self): + pass + + def addAsyncCleanup(self, func, /, *args, **kwargs): + # A trivial trampoline to addCleanup() + # the function exists because it has a different semantics + # and signature: + # addCleanup() accepts regular functions + # but addAsyncCleanup() accepts coroutines + # + # We intentionally don't add inspect.iscoroutinefunction() check + # for func argument because there is no way + # to check for async function reliably: + # 1. It can be "async def func()" iself + # 2. Class can implement "async def __call__()" method + # 3. Regular "def func()" that returns awaitable object + self.addCleanup(*(func, *args), **kwargs) + + def _callSetUp(self): + self.setUp() + self._callAsync(self.asyncSetUp) + + def _callTestMethod(self, method): + self._callMaybeAsync(method) + + def _callTearDown(self): + self._callAsync(self.asyncTearDown) + self.tearDown() + + def _callCleanup(self, function, *args, **kwargs): + self._callMaybeAsync(function, *args, **kwargs) + + def _callAsync(self, func, /, *args, **kwargs): + assert self._asyncioTestLoop is not None + ret = func(*args, **kwargs) + assert inspect.isawaitable(ret) + fut = self._asyncioTestLoop.create_future() + self._asyncioCallsQueue.put_nowait((fut, ret)) + return self._asyncioTestLoop.run_until_complete(fut) + + def _callMaybeAsync(self, func, /, *args, **kwargs): + assert self._asyncioTestLoop is not None + ret = func(*args, **kwargs) + if inspect.isawaitable(ret): + fut = self._asyncioTestLoop.create_future() + self._asyncioCallsQueue.put_nowait((fut, ret)) + return self._asyncioTestLoop.run_until_complete(fut) + else: + return ret + + async def _asyncioLoopRunner(self, fut): + self._asyncioCallsQueue = queue = asyncio.Queue() + fut.set_result(None) + while True: + query = await queue.get() + queue.task_done() + if query is None: + return + fut, awaitable = query + try: + ret = await awaitable + if not fut.cancelled(): + fut.set_result(ret) + except (SystemExit, KeyboardInterrupt): + raise + except (BaseException, asyncio.CancelledError) as ex: + if not fut.cancelled(): + fut.set_exception(ex) + + def _setupAsyncioLoop(self): + assert self._asyncioTestLoop is None + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.set_debug(True) + self._asyncioTestLoop = loop + fut = loop.create_future() + self._asyncioCallsTask = loop.create_task(self._asyncioLoopRunner(fut)) + loop.run_until_complete(fut) + + def _tearDownAsyncioLoop(self): + assert self._asyncioTestLoop is not None + loop = self._asyncioTestLoop + self._asyncioTestLoop = None + self._asyncioCallsQueue.put_nowait(None) + loop.run_until_complete(self._asyncioCallsQueue.join()) + + try: + # cancel all tasks + to_cancel = asyncio.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete( + asyncio.gather(*to_cancel, loop=loop, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler({ + 'message': 'unhandled exception during test shutdown', + 'exception': task.exception(), + 'task': task, + }) + # shutdown asyncgens + loop.run_until_complete(loop.shutdown_asyncgens()) + finally: + asyncio.set_event_loop(None) + loop.close() + + def run(self, result=None): + self._setupAsyncioLoop() + try: + return super().run(result) + finally: + self._tearDownAsyncioLoop() diff --git a/Monika After Story/game/python-packages/unittest/case.py b/Monika After Story/game/python-packages/unittest/case.py new file mode 100644 index 0000000000..f8bc865ee8 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/case.py @@ -0,0 +1,1437 @@ +"""Test case implementation""" + +import sys +import functools +import difflib +import pprint +import re +import warnings +import collections +import contextlib +import traceback +import types + +from . import result +from .util import (strclass, safe_repr, _count_diff_all_purpose, + _count_diff_hashable, _common_shorten_repr) + +__unittest = True + +_subtest_msg_sentinel = object() + +DIFF_OMITTED = ('\nDiff is %s characters long. ' + 'Set self.maxDiff to None to see it.') + +class SkipTest(Exception): + """ + Raise this exception in a test to skip it. + + Usually you can use TestCase.skipTest() or one of the skipping decorators + instead of raising this directly. + """ + +class _ShouldStop(Exception): + """ + The test should stop. + """ + +class _UnexpectedSuccess(Exception): + """ + The test was supposed to fail, but it didn't! + """ + + +class _Outcome(object): + def __init__(self, result=None): + self.expecting_failure = False + self.result = result + self.result_supports_subtests = hasattr(result, "addSubTest") + self.success = True + self.skipped = [] + self.expectedFailure = None + self.errors = [] + + @contextlib.contextmanager + def testPartExecutor(self, test_case, isTest=False): + old_success = self.success + self.success = True + try: + yield + except KeyboardInterrupt: + raise + except SkipTest as e: + self.success = False + self.skipped.append((test_case, str(e))) + except _ShouldStop: + pass + except: + exc_info = sys.exc_info() + if self.expecting_failure: + self.expectedFailure = exc_info + else: + self.success = False + self.errors.append((test_case, exc_info)) + # explicitly break a reference cycle: + # exc_info -> frame -> exc_info + exc_info = None + else: + if self.result_supports_subtests and self.success: + self.errors.append((test_case, None)) + finally: + self.success = self.success and old_success + + +def _id(obj): + return obj + + +_module_cleanups = [] +def addModuleCleanup(function, /, *args, **kwargs): + """Same as addCleanup, except the cleanup items are called even if + setUpModule fails (unlike tearDownModule).""" + _module_cleanups.append((function, args, kwargs)) + + +def doModuleCleanups(): + """Execute all module cleanup functions. Normally called for you after + tearDownModule.""" + exceptions = [] + while _module_cleanups: + function, args, kwargs = _module_cleanups.pop() + try: + function(*args, **kwargs) + except Exception as exc: + exceptions.append(exc) + if exceptions: + # Swallows all but first exception. If a multi-exception handler + # gets written we should use that here instead. + raise exceptions[0] + + +def skip(reason): + """ + Unconditionally skip a test. + """ + def decorator(test_item): + if not isinstance(test_item, type): + @functools.wraps(test_item) + def skip_wrapper(*args, **kwargs): + raise SkipTest(reason) + test_item = skip_wrapper + + test_item.__unittest_skip__ = True + test_item.__unittest_skip_why__ = reason + return test_item + if isinstance(reason, types.FunctionType): + test_item = reason + reason = '' + return decorator(test_item) + return decorator + +def skipIf(condition, reason): + """ + Skip a test if the condition is true. + """ + if condition: + return skip(reason) + return _id + +def skipUnless(condition, reason): + """ + Skip a test unless the condition is true. + """ + if not condition: + return skip(reason) + return _id + +def expectedFailure(test_item): + test_item.__unittest_expecting_failure__ = True + return test_item + +def _is_subtype(expected, basetype): + if isinstance(expected, tuple): + return all(_is_subtype(e, basetype) for e in expected) + return isinstance(expected, type) and issubclass(expected, basetype) + +class _BaseTestCaseContext: + + def __init__(self, test_case): + self.test_case = test_case + + def _raiseFailure(self, standardMsg): + msg = self.test_case._formatMessage(self.msg, standardMsg) + raise self.test_case.failureException(msg) + +class _AssertRaisesBaseContext(_BaseTestCaseContext): + + def __init__(self, expected, test_case, expected_regex=None): + _BaseTestCaseContext.__init__(self, test_case) + self.expected = expected + self.test_case = test_case + if expected_regex is not None: + expected_regex = re.compile(expected_regex) + self.expected_regex = expected_regex + self.obj_name = None + self.msg = None + + def handle(self, name, args, kwargs): + """ + If args is empty, assertRaises/Warns is being used as a + context manager, so check for a 'msg' kwarg and return self. + If args is not empty, call a callable passing positional and keyword + arguments. + """ + try: + if not _is_subtype(self.expected, self._base_type): + raise TypeError('%s() arg 1 must be %s' % + (name, self._base_type_str)) + if not args: + self.msg = kwargs.pop('msg', None) + if kwargs: + raise TypeError('%r is an invalid keyword argument for ' + 'this function' % (next(iter(kwargs)),)) + return self + + callable_obj, *args = args + try: + self.obj_name = callable_obj.__name__ + except AttributeError: + self.obj_name = str(callable_obj) + with self: + callable_obj(*args, **kwargs) + finally: + # bpo-23890: manually break a reference cycle + self = None + + +class _AssertRaisesContext(_AssertRaisesBaseContext): + """A context manager used to implement TestCase.assertRaises* methods.""" + + _base_type = BaseException + _base_type_str = 'an exception type or tuple of exception types' + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + if self.obj_name: + self._raiseFailure("{} not raised by {}".format(exc_name, + self.obj_name)) + else: + self._raiseFailure("{} not raised".format(exc_name)) + else: + traceback.clear_frames(tb) + if not issubclass(exc_type, self.expected): + # let unexpected exceptions pass through + return False + # store exception, without traceback, for later retrieval + self.exception = exc_value.with_traceback(None) + if self.expected_regex is None: + return True + + expected_regex = self.expected_regex + if not expected_regex.search(str(exc_value)): + self._raiseFailure('"{}" does not match "{}"'.format( + expected_regex.pattern, str(exc_value))) + return True + + __class_getitem__ = classmethod(types.GenericAlias) + + +class _AssertWarnsContext(_AssertRaisesBaseContext): + """A context manager used to implement TestCase.assertWarns* methods.""" + + _base_type = Warning + _base_type_str = 'a warning type or tuple of warning types' + + def __enter__(self): + # The __warningregistry__'s need to be in a pristine state for tests + # to work properly. + for v in sys.modules.values(): + if getattr(v, '__warningregistry__', None): + v.__warningregistry__ = {} + self.warnings_manager = warnings.catch_warnings(record=True) + self.warnings = self.warnings_manager.__enter__() + warnings.simplefilter("always", self.expected) + return self + + def __exit__(self, exc_type, exc_value, tb): + self.warnings_manager.__exit__(exc_type, exc_value, tb) + if exc_type is not None: + # let unexpected exceptions pass through + return + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + first_matching = None + for m in self.warnings: + w = m.message + if not isinstance(w, self.expected): + continue + if first_matching is None: + first_matching = w + if (self.expected_regex is not None and + not self.expected_regex.search(str(w))): + continue + # store warning for later retrieval + self.warning = w + self.filename = m.filename + self.lineno = m.lineno + return + # Now we simply try to choose a helpful failure message + if first_matching is not None: + self._raiseFailure('"{}" does not match "{}"'.format( + self.expected_regex.pattern, str(first_matching))) + if self.obj_name: + self._raiseFailure("{} not triggered by {}".format(exc_name, + self.obj_name)) + else: + self._raiseFailure("{} not triggered".format(exc_name)) + + + +class _OrderedChainMap(collections.ChainMap): + def __iter__(self): + seen = set() + for mapping in self.maps: + for k in mapping: + if k not in seen: + seen.add(k) + yield k + + +class TestCase(object): + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + + When subclassing TestCase, you can set these attributes: + * failureException: determines which exception will be raised when + the instance's assertion methods fail; test methods raising this + exception will be deemed to have 'failed' rather than 'errored'. + * longMessage: determines whether long messages (including repr of + objects used in assert methods) will be printed on failure in *addition* + to any explicit message passed. + * maxDiff: sets the maximum length of a diff in failure messages + by assert methods using difflib. It is looked up as an instance + attribute so can be configured by individual tests if required. + """ + + failureException = AssertionError + + longMessage = True + + maxDiff = 80*8 + + # If a string is longer than _diffThreshold, use normal comparison instead + # of difflib. See #11763. + _diffThreshold = 2**16 + + # Attribute used by TestSuite for classSetUp + + _classSetupFailed = False + + _class_cleanups = [] + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + self._testMethodName = methodName + self._outcome = None + self._testMethodDoc = 'No test' + try: + testMethod = getattr(self, methodName) + except AttributeError: + if methodName != 'runTest': + # we allow instantiation with no explicit method name + # but not an *incorrect* or missing method name + raise ValueError("no such test method in %s: %s" % + (self.__class__, methodName)) + else: + self._testMethodDoc = testMethod.__doc__ + self._cleanups = [] + self._subtest = None + + # Map types to custom assertEqual functions that will compare + # instances of said type in more detail to generate a more useful + # error message. + self._type_equality_funcs = {} + self.addTypeEqualityFunc(dict, 'assertDictEqual') + self.addTypeEqualityFunc(list, 'assertListEqual') + self.addTypeEqualityFunc(tuple, 'assertTupleEqual') + self.addTypeEqualityFunc(set, 'assertSetEqual') + self.addTypeEqualityFunc(frozenset, 'assertSetEqual') + self.addTypeEqualityFunc(str, 'assertMultiLineEqual') + + def addTypeEqualityFunc(self, typeobj, function): + """Add a type specific assertEqual style function to compare a type. + + This method is for use by TestCase subclasses that need to register + their own type equality functions to provide nicer error messages. + + Args: + typeobj: The data type to call this function on when both values + are of the same type in assertEqual(). + function: The callable taking two arguments and an optional + msg= argument that raises self.failureException with a + useful error message when the two arguments are not equal. + """ + self._type_equality_funcs[typeobj] = function + + def addCleanup(self, function, /, *args, **kwargs): + """Add a function, with arguments, to be called when the test is + completed. Functions added are called on a LIFO basis and are + called after tearDown on test failure or success. + + Cleanup items are called even if setUp fails (unlike tearDown).""" + self._cleanups.append((function, args, kwargs)) + + @classmethod + def addClassCleanup(cls, function, /, *args, **kwargs): + """Same as addCleanup, except the cleanup items are called even if + setUpClass fails (unlike tearDownClass).""" + cls._class_cleanups.append((function, args, kwargs)) + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + pass + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + pass + + @classmethod + def setUpClass(cls): + "Hook method for setting up class fixture before running tests in the class." + + @classmethod + def tearDownClass(cls): + "Hook method for deconstructing the class fixture after running all tests in the class." + + def countTestCases(self): + return 1 + + def defaultTestResult(self): + return result.TestResult() + + def shortDescription(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + doc = self._testMethodDoc + return doc.strip().split("\n")[0].strip() if doc else None + + + def id(self): + return "%s.%s" % (strclass(self.__class__), self._testMethodName) + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self._testMethodName == other._testMethodName + + def __hash__(self): + return hash((type(self), self._testMethodName)) + + def __str__(self): + return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (strclass(self.__class__), self._testMethodName) + + def _addSkip(self, result, test_case, reason): + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None: + addSkip(test_case, reason) + else: + warnings.warn("TestResult has no addSkip method, skips not reported", + RuntimeWarning, 2) + result.addSuccess(test_case) + + @contextlib.contextmanager + def subTest(self, msg=_subtest_msg_sentinel, **params): + """Return a context manager that will return the enclosed block + of code in a subtest identified by the optional message and + keyword parameters. A failure in the subtest marks the test + case as failed but resumes execution at the end of the enclosed + block, allowing further test code to be executed. + """ + if self._outcome is None or not self._outcome.result_supports_subtests: + yield + return + parent = self._subtest + if parent is None: + params_map = _OrderedChainMap(params) + else: + params_map = parent.params.new_child(params) + self._subtest = _SubTest(self, msg, params_map) + try: + with self._outcome.testPartExecutor(self._subtest, isTest=True): + yield + if not self._outcome.success: + result = self._outcome.result + if result is not None and result.failfast: + raise _ShouldStop + elif self._outcome.expectedFailure: + # If the test is expecting a failure, we really want to + # stop now and register the expected failure. + raise _ShouldStop + finally: + self._subtest = parent + + def _feedErrorsToResult(self, result, errors): + for test, exc_info in errors: + if isinstance(test, _SubTest): + result.addSubTest(test.test_case, test, exc_info) + elif exc_info is not None: + if issubclass(exc_info[0], self.failureException): + result.addFailure(test, exc_info) + else: + result.addError(test, exc_info) + + def _addExpectedFailure(self, result, exc_info): + try: + addExpectedFailure = result.addExpectedFailure + except AttributeError: + warnings.warn("TestResult has no addExpectedFailure method, reporting as passes", + RuntimeWarning) + result.addSuccess(self) + else: + addExpectedFailure(self, exc_info) + + def _addUnexpectedSuccess(self, result): + try: + addUnexpectedSuccess = result.addUnexpectedSuccess + except AttributeError: + warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failure", + RuntimeWarning) + # We need to pass an actual exception and traceback to addFailure, + # otherwise the legacy result can choke. + try: + raise _UnexpectedSuccess from None + except _UnexpectedSuccess: + result.addFailure(self, sys.exc_info()) + else: + addUnexpectedSuccess(self) + + def _callSetUp(self): + self.setUp() + + def _callTestMethod(self, method): + method() + + def _callTearDown(self): + self.tearDown() + + def _callCleanup(self, function, /, *args, **kwargs): + function(*args, **kwargs) + + def run(self, result=None): + orig_result = result + if result is None: + result = self.defaultTestResult() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + + result.startTest(self) + + testMethod = getattr(self, self._testMethodName) + if (getattr(self.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + try: + skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') + or getattr(testMethod, '__unittest_skip_why__', '')) + self._addSkip(result, self, skip_why) + finally: + result.stopTest(self) + return + expecting_failure_method = getattr(testMethod, + "__unittest_expecting_failure__", False) + expecting_failure_class = getattr(self, + "__unittest_expecting_failure__", False) + expecting_failure = expecting_failure_class or expecting_failure_method + outcome = _Outcome(result) + try: + self._outcome = outcome + + with outcome.testPartExecutor(self): + self._callSetUp() + if outcome.success: + outcome.expecting_failure = expecting_failure + with outcome.testPartExecutor(self, isTest=True): + self._callTestMethod(testMethod) + outcome.expecting_failure = False + with outcome.testPartExecutor(self): + self._callTearDown() + + self.doCleanups() + for test, reason in outcome.skipped: + self._addSkip(result, test, reason) + self._feedErrorsToResult(result, outcome.errors) + if outcome.success: + if expecting_failure: + if outcome.expectedFailure: + self._addExpectedFailure(result, outcome.expectedFailure) + else: + self._addUnexpectedSuccess(result) + else: + result.addSuccess(self) + return result + finally: + result.stopTest(self) + if orig_result is None: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + + # explicitly break reference cycles: + # outcome.errors -> frame -> outcome -> outcome.errors + # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure + outcome.errors.clear() + outcome.expectedFailure = None + + # clear the outcome, no more needed + self._outcome = None + + def doCleanups(self): + """Execute all cleanup functions. Normally called for you after + tearDown.""" + outcome = self._outcome or _Outcome() + while self._cleanups: + function, args, kwargs = self._cleanups.pop() + with outcome.testPartExecutor(self): + self._callCleanup(function, *args, **kwargs) + + # return this for backwards compatibility + # even though we no longer use it internally + return outcome.success + + @classmethod + def doClassCleanups(cls): + """Execute all class cleanup functions. Normally called for you after + tearDownClass.""" + cls.tearDown_exceptions = [] + while cls._class_cleanups: + function, args, kwargs = cls._class_cleanups.pop() + try: + function(*args, **kwargs) + except Exception: + cls.tearDown_exceptions.append(sys.exc_info()) + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + getattr(self, self._testMethodName)() + self.tearDown() + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + function(*args, **kwargs) + + def skipTest(self, reason): + """Skip this test.""" + raise SkipTest(reason) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException(msg) + + def assertFalse(self, expr, msg=None): + """Check that the expression is false.""" + if expr: + msg = self._formatMessage(msg, "%s is not false" % safe_repr(expr)) + raise self.failureException(msg) + + def assertTrue(self, expr, msg=None): + """Check that the expression is true.""" + if not expr: + msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr)) + raise self.failureException(msg) + + def _formatMessage(self, msg, standardMsg): + """Honour the longMessage attribute when generating failure messages. + If longMessage is False this means: + * Use only an explicit message if it is provided + * Otherwise use the standard message for the assert + + If longMessage is True: + * Use the standard message + * If an explicit message is provided, plus ' : ' and the explicit message + """ + if not self.longMessage: + return msg or standardMsg + if msg is None: + return standardMsg + try: + # don't switch to '{}' formatting in Python 2.X + # it changes the way unicode input is handled + return '%s : %s' % (standardMsg, msg) + except UnicodeDecodeError: + return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) + + def assertRaises(self, expected_exception, *args, **kwargs): + """Fail unless an exception of class expected_exception is raised + by the callable when invoked with specified positional and + keyword arguments. If a different type of exception is + raised, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + + If called with the callable and arguments omitted, will return a + context object used like this:: + + with self.assertRaises(SomeException): + do_something() + + An optional keyword argument 'msg' can be provided when assertRaises + is used as a context object. + + The context manager keeps a reference to the exception as + the 'exception' attribute. This allows you to inspect the + exception after the assertion:: + + with self.assertRaises(SomeException) as cm: + do_something() + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) + """ + context = _AssertRaisesContext(expected_exception, self) + try: + return context.handle('assertRaises', args, kwargs) + finally: + # bpo-23890: manually break a reference cycle + context = None + + def assertWarns(self, expected_warning, *args, **kwargs): + """Fail unless a warning of class warnClass is triggered + by the callable when invoked with specified positional and + keyword arguments. If a different type of warning is + triggered, it will not be handled: depending on the other + warning filtering rules in effect, it might be silenced, printed + out, or raised as an exception. + + If called with the callable and arguments omitted, will return a + context object used like this:: + + with self.assertWarns(SomeWarning): + do_something() + + An optional keyword argument 'msg' can be provided when assertWarns + is used as a context object. + + The context manager keeps a reference to the first matching + warning as the 'warning' attribute; similarly, the 'filename' + and 'lineno' attributes give you information about the line + of Python code from which the warning was triggered. + This allows you to inspect the warning after the assertion:: + + with self.assertWarns(SomeWarning) as cm: + do_something() + the_warning = cm.warning + self.assertEqual(the_warning.some_attribute, 147) + """ + context = _AssertWarnsContext(expected_warning, self) + return context.handle('assertWarns', args, kwargs) + + def assertLogs(self, logger=None, level=None): + """Fail unless a log message of level *level* or higher is emitted + on *logger_name* or its children. If omitted, *level* defaults to + INFO and *logger* defaults to the root logger. + + This method must be used as a context manager, and will yield + a recording object with two attributes: `output` and `records`. + At the end of the context manager, the `output` attribute will + be a list of the matching formatted log messages and the + `records` attribute will be a list of the corresponding LogRecord + objects. + + Example:: + + with self.assertLogs('foo', level='INFO') as cm: + logging.getLogger('foo').info('first message') + logging.getLogger('foo.bar').error('second message') + self.assertEqual(cm.output, ['INFO:foo:first message', + 'ERROR:foo.bar:second message']) + """ + # Lazy import to avoid importing logging if it is not needed. + from ._log import _AssertLogsContext + return _AssertLogsContext(self, logger, level) + + def _getAssertEqualityFunc(self, first, second): + """Get a detailed comparison function for the types of the two args. + + Returns: A callable accepting (first, second, msg=None) that will + raise a failure exception if first != second with a useful human + readable error message for those types. + """ + # + # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) + # and vice versa. I opted for the conservative approach in case + # subclasses are not intended to be compared in detail to their super + # class instances using a type equality func. This means testing + # subtypes won't automagically use the detailed comparison. Callers + # should use their type specific assertSpamEqual method to compare + # subclasses if the detailed comparison is desired and appropriate. + # See the discussion in http://bugs.python.org/issue2578. + # + if type(first) is type(second): + asserter = self._type_equality_funcs.get(type(first)) + if asserter is not None: + if isinstance(asserter, str): + asserter = getattr(self, asserter) + return asserter + + return self._baseAssertEqual + + def _baseAssertEqual(self, first, second, msg=None): + """The default assertEqual implementation, not type specific.""" + if not first == second: + standardMsg = '%s != %s' % _common_shorten_repr(first, second) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' + operator. + """ + assertion_func = self._getAssertEqualityFunc(first, second) + assertion_func(first, second, msg=msg) + + def assertNotEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '!=' + operator. + """ + if not first != second: + msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), + safe_repr(second))) + raise self.failureException(msg) + + def assertAlmostEqual(self, first, second, places=None, msg=None, + delta=None): + """Fail if the two objects are unequal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + difference between the two objects is more than the given + delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most significant digit). + + If the two objects compare equal then they will automatically + compare almost equal. + """ + if first == second: + # shortcut + return + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + diff = abs(first - second) + if delta is not None: + if diff <= delta: + return + + standardMsg = '%s != %s within %s delta (%s difference)' % ( + safe_repr(first), + safe_repr(second), + safe_repr(delta), + safe_repr(diff)) + else: + if places is None: + places = 7 + + if round(diff, places) == 0: + return + + standardMsg = '%s != %s within %r places (%s difference)' % ( + safe_repr(first), + safe_repr(second), + places, + safe_repr(diff)) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertNotAlmostEqual(self, first, second, places=None, msg=None, + delta=None): + """Fail if the two objects are equal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + difference between the two objects is less than the given delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most significant digit). + + Objects that are equal automatically fail. + """ + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + diff = abs(first - second) + if delta is not None: + if not (first == second) and diff > delta: + return + standardMsg = '%s == %s within %s delta (%s difference)' % ( + safe_repr(first), + safe_repr(second), + safe_repr(delta), + safe_repr(diff)) + else: + if places is None: + places = 7 + if not (first == second) and round(diff, places) != 0: + return + standardMsg = '%s == %s within %r places' % (safe_repr(first), + safe_repr(second), + places) + + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertSequenceEqual(self, seq1, seq2, msg=None, seq_type=None): + """An equality assertion for ordered sequences (like lists and tuples). + + For the purposes of this function, a valid ordered sequence type is one + which can be indexed, has a length, and has an equality operator. + + Args: + seq1: The first sequence to compare. + seq2: The second sequence to compare. + seq_type: The expected datatype of the sequences, or None if no + datatype should be enforced. + msg: Optional message to use on failure instead of a list of + differences. + """ + if seq_type is not None: + seq_type_name = seq_type.__name__ + if not isinstance(seq1, seq_type): + raise self.failureException('First sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq1))) + if not isinstance(seq2, seq_type): + raise self.failureException('Second sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq2))) + else: + seq_type_name = "sequence" + + differing = None + try: + len1 = len(seq1) + except (TypeError, NotImplementedError): + differing = 'First %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + try: + len2 = len(seq2) + except (TypeError, NotImplementedError): + differing = 'Second %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + if seq1 == seq2: + return + + differing = '%ss differ: %s != %s\n' % ( + (seq_type_name.capitalize(),) + + _common_shorten_repr(seq1, seq2)) + + for i in range(min(len1, len2)): + try: + item1 = seq1[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of first %s\n' % + (i, seq_type_name)) + break + + try: + item2 = seq2[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of second %s\n' % + (i, seq_type_name)) + break + + if item1 != item2: + differing += ('\nFirst differing element %d:\n%s\n%s\n' % + ((i,) + _common_shorten_repr(item1, item2))) + break + else: + if (len1 == len2 and seq_type is None and + type(seq1) != type(seq2)): + # The sequences are the same, but have differing types. + return + + if len1 > len2: + differing += ('\nFirst %s contains %d additional ' + 'elements.\n' % (seq_type_name, len1 - len2)) + try: + differing += ('First extra element %d:\n%s\n' % + (len2, safe_repr(seq1[len2]))) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of first %s\n' % (len2, seq_type_name)) + elif len1 < len2: + differing += ('\nSecond %s contains %d additional ' + 'elements.\n' % (seq_type_name, len2 - len1)) + try: + differing += ('First extra element %d:\n%s\n' % + (len1, safe_repr(seq2[len1]))) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of second %s\n' % (len1, seq_type_name)) + standardMsg = differing + diffMsg = '\n' + '\n'.join( + difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + + standardMsg = self._truncateMessage(standardMsg, diffMsg) + msg = self._formatMessage(msg, standardMsg) + self.fail(msg) + + def _truncateMessage(self, message, diff): + max_diff = self.maxDiff + if max_diff is None or len(diff) <= max_diff: + return message + diff + return message + (DIFF_OMITTED % len(diff)) + + def assertListEqual(self, list1, list2, msg=None): + """A list-specific equality assertion. + + Args: + list1: The first list to compare. + list2: The second list to compare. + msg: Optional message to use on failure instead of a list of + differences. + + """ + self.assertSequenceEqual(list1, list2, msg, seq_type=list) + + def assertTupleEqual(self, tuple1, tuple2, msg=None): + """A tuple-specific equality assertion. + + Args: + tuple1: The first tuple to compare. + tuple2: The second tuple to compare. + msg: Optional message to use on failure instead of a list of + differences. + """ + self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) + + def assertSetEqual(self, set1, set2, msg=None): + """A set-specific equality assertion. + + Args: + set1: The first set to compare. + set2: The second set to compare. + msg: Optional message to use on failure instead of a list of + differences. + + assertSetEqual uses ducktyping to support different types of sets, and + is optimized for sets specifically (parameters must support a + difference method). + """ + try: + difference1 = set1.difference(set2) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('first argument does not support set difference: %s' % e) + + try: + difference2 = set2.difference(set1) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('second argument does not support set difference: %s' % e) + + if not (difference1 or difference2): + return + + lines = [] + if difference1: + lines.append('Items in the first set but not the second:') + for item in difference1: + lines.append(repr(item)) + if difference2: + lines.append('Items in the second set but not the first:') + for item in difference2: + lines.append(repr(item)) + + standardMsg = '\n'.join(lines) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%s not found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b), but with a nicer default message.""" + if member in container: + standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIs(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is b), but with a nicer default message.""" + if expr1 is not expr2: + standardMsg = '%s is not %s' % (safe_repr(expr1), + safe_repr(expr2)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNot(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is not b), but with a nicer default message.""" + if expr1 is expr2: + standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictEqual(self, d1, d2, msg=None): + self.assertIsInstance(d1, dict, 'First argument is not a dictionary') + self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') + + if d1 != d2: + standardMsg = '%s != %s' % _common_shorten_repr(d1, d2) + diff = ('\n' + '\n'.join(difflib.ndiff( + pprint.pformat(d1).splitlines(), + pprint.pformat(d2).splitlines()))) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictContainsSubset(self, subset, dictionary, msg=None): + """Checks whether dictionary is a superset of subset.""" + warnings.warn('assertDictContainsSubset is deprecated', + DeprecationWarning) + missing = [] + mismatched = [] + for key, value in subset.items(): + if key not in dictionary: + missing.append(key) + elif value != dictionary[key]: + mismatched.append('%s, expected: %s, actual: %s' % + (safe_repr(key), safe_repr(value), + safe_repr(dictionary[key]))) + + if not (missing or mismatched): + return + + standardMsg = '' + if missing: + standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in + missing) + if mismatched: + if standardMsg: + standardMsg += '; ' + standardMsg += 'Mismatched values: %s' % ','.join(mismatched) + + self.fail(self._formatMessage(msg, standardMsg)) + + + def assertCountEqual(self, first, second, msg=None): + """Asserts that two iterables have the same elements, the same number of + times, without regard to order. + + self.assertEqual(Counter(list(first)), + Counter(list(second))) + + Example: + - [0, 1, 1] and [1, 0, 1] compare equal. + - [0, 0, 1] and [0, 1] compare unequal. + + """ + first_seq, second_seq = list(first), list(second) + try: + first = collections.Counter(first_seq) + second = collections.Counter(second_seq) + except TypeError: + # Handle case with unhashable elements + differences = _count_diff_all_purpose(first_seq, second_seq) + else: + if first == second: + return + differences = _count_diff_hashable(first_seq, second_seq) + + if differences: + standardMsg = 'Element counts were not equal:\n' + lines = ['First has %d, Second has %d: %r' % diff for diff in differences] + diffMsg = '\n'.join(lines) + standardMsg = self._truncateMessage(standardMsg, diffMsg) + msg = self._formatMessage(msg, standardMsg) + self.fail(msg) + + def assertMultiLineEqual(self, first, second, msg=None): + """Assert that two multi-line strings are equal.""" + self.assertIsInstance(first, str, 'First argument is not a string') + self.assertIsInstance(second, str, 'Second argument is not a string') + + if first != second: + # don't use difflib if the strings are too long + if (len(first) > self._diffThreshold or + len(second) > self._diffThreshold): + self._baseAssertEqual(first, second, msg) + firstlines = first.splitlines(keepends=True) + secondlines = second.splitlines(keepends=True) + if len(firstlines) == 1 and first.strip('\r\n') == first: + firstlines = [first + '\n'] + secondlines = [second + '\n'] + standardMsg = '%s != %s' % _common_shorten_repr(first, second) + diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines)) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLess(self, a, b, msg=None): + """Just like self.assertTrue(a < b), but with a nicer default message.""" + if not a < b: + standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLessEqual(self, a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default message.""" + if not a <= b: + standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreater(self, a, b, msg=None): + """Just like self.assertTrue(a > b), but with a nicer default message.""" + if not a > b: + standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreaterEqual(self, a, b, msg=None): + """Just like self.assertTrue(a >= b), but with a nicer default message.""" + if not a >= b: + standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNone(self, obj, msg=None): + """Same as self.assertTrue(obj is None), with a nicer default message.""" + if obj is not None: + standardMsg = '%s is not None' % (safe_repr(obj),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNotNone(self, obj, msg=None): + """Included for symmetry with assertIsNone.""" + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)), with a nicer + default message.""" + if not isinstance(obj, cls): + standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIsInstance(self, obj, cls, msg=None): + """Included for symmetry with assertIsInstance.""" + if isinstance(obj, cls): + standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertRaisesRegex(self, expected_exception, expected_regex, + *args, **kwargs): + """Asserts that the message in a raised exception matches a regex. + + Args: + expected_exception: Exception class expected to be raised. + expected_regex: Regex (re.Pattern object or string) expected + to be found in error message. + args: Function to be called and extra positional args. + kwargs: Extra kwargs. + msg: Optional message used in case of failure. Can only be used + when assertRaisesRegex is used as a context manager. + """ + context = _AssertRaisesContext(expected_exception, self, expected_regex) + return context.handle('assertRaisesRegex', args, kwargs) + + def assertWarnsRegex(self, expected_warning, expected_regex, + *args, **kwargs): + """Asserts that the message in a triggered warning matches a regexp. + Basic functioning is similar to assertWarns() with the addition + that only warnings whose messages also match the regular expression + are considered successful matches. + + Args: + expected_warning: Warning class expected to be triggered. + expected_regex: Regex (re.Pattern object or string) expected + to be found in error message. + args: Function to be called and extra positional args. + kwargs: Extra kwargs. + msg: Optional message used in case of failure. Can only be used + when assertWarnsRegex is used as a context manager. + """ + context = _AssertWarnsContext(expected_warning, self, expected_regex) + return context.handle('assertWarnsRegex', args, kwargs) + + def assertRegex(self, text, expected_regex, msg=None): + """Fail the test unless the text matches the regular expression.""" + if isinstance(expected_regex, (str, bytes)): + assert expected_regex, "expected_regex must not be empty." + expected_regex = re.compile(expected_regex) + if not expected_regex.search(text): + standardMsg = "Regex didn't match: %r not found in %r" % ( + expected_regex.pattern, text) + # _formatMessage ensures the longMessage option is respected + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertNotRegex(self, text, unexpected_regex, msg=None): + """Fail the test if the text matches the regular expression.""" + if isinstance(unexpected_regex, (str, bytes)): + unexpected_regex = re.compile(unexpected_regex) + match = unexpected_regex.search(text) + if match: + standardMsg = 'Regex matched: %r matches %r in %r' % ( + text[match.start() : match.end()], + unexpected_regex.pattern, + text) + # _formatMessage ensures the longMessage option is respected + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + + def _deprecate(original_func): + def deprecated_func(*args, **kwargs): + warnings.warn( + 'Please use {0} instead.'.format(original_func.__name__), + DeprecationWarning, 2) + return original_func(*args, **kwargs) + return deprecated_func + + # see #9424 + failUnlessEqual = assertEquals = _deprecate(assertEqual) + failIfEqual = assertNotEquals = _deprecate(assertNotEqual) + failUnlessAlmostEqual = assertAlmostEquals = _deprecate(assertAlmostEqual) + failIfAlmostEqual = assertNotAlmostEquals = _deprecate(assertNotAlmostEqual) + failUnless = assert_ = _deprecate(assertTrue) + failUnlessRaises = _deprecate(assertRaises) + failIf = _deprecate(assertFalse) + assertRaisesRegexp = _deprecate(assertRaisesRegex) + assertRegexpMatches = _deprecate(assertRegex) + assertNotRegexpMatches = _deprecate(assertNotRegex) + + + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + unittest framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, description=None): + super(FunctionTestCase, self).__init__() + self._setUpFunc = setUp + self._tearDownFunc = tearDown + self._testFunc = testFunc + self._description = description + + def setUp(self): + if self._setUpFunc is not None: + self._setUpFunc() + + def tearDown(self): + if self._tearDownFunc is not None: + self._tearDownFunc() + + def runTest(self): + self._testFunc() + + def id(self): + return self._testFunc.__name__ + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self._setUpFunc == other._setUpFunc and \ + self._tearDownFunc == other._tearDownFunc and \ + self._testFunc == other._testFunc and \ + self._description == other._description + + def __hash__(self): + return hash((type(self), self._setUpFunc, self._tearDownFunc, + self._testFunc, self._description)) + + def __str__(self): + return "%s (%s)" % (strclass(self.__class__), + self._testFunc.__name__) + + def __repr__(self): + return "<%s tec=%s>" % (strclass(self.__class__), + self._testFunc) + + def shortDescription(self): + if self._description is not None: + return self._description + doc = self._testFunc.__doc__ + return doc and doc.split("\n")[0].strip() or None + + +class _SubTest(TestCase): + + def __init__(self, test_case, message, params): + super().__init__() + self._message = message + self.test_case = test_case + self.params = params + self.failureException = test_case.failureException + + def runTest(self): + raise NotImplementedError("subtests cannot be run directly") + + def _subDescription(self): + parts = [] + if self._message is not _subtest_msg_sentinel: + parts.append("[{}]".format(self._message)) + if self.params: + params_desc = ', '.join( + "{}={!r}".format(k, v) + for (k, v) in self.params.items()) + parts.append("({})".format(params_desc)) + return " ".join(parts) or '()' + + def id(self): + return "{} {}".format(self.test_case.id(), self._subDescription()) + + def shortDescription(self): + """Returns a one-line description of the subtest, or None if no + description has been provided. + """ + return self.test_case.shortDescription() + + def __str__(self): + return "{} {}".format(self.test_case, self._subDescription()) diff --git a/Monika After Story/game/python-packages/unittest/loader.py b/Monika After Story/game/python-packages/unittest/loader.py new file mode 100644 index 0000000000..ba7105e1ad --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/loader.py @@ -0,0 +1,517 @@ +"""Loading unittests.""" + +import os +import re +import sys +import traceback +import types +import functools +import warnings + +from fnmatch import fnmatch, fnmatchcase + +from . import case, suite, util + +__unittest = True + +# what about .pyc (etc) +# we would need to avoid loading the same tests multiple times +# from '.py', *and* '.pyc' +VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) + + +class _FailedTest(case.TestCase): + _testMethodName = None + + def __init__(self, method_name, exception): + self._exception = exception + super(_FailedTest, self).__init__(method_name) + + def __getattr__(self, name): + if name != self._testMethodName: + return super(_FailedTest, self).__getattr__(name) + def testFailure(): + raise self._exception + return testFailure + + +def _make_failed_import_test(name, suiteClass): + message = 'Failed to import test module: %s\n%s' % ( + name, traceback.format_exc()) + return _make_failed_test(name, ImportError(message), suiteClass, message) + +def _make_failed_load_tests(name, exception, suiteClass): + message = 'Failed to call load_tests:\n%s' % (traceback.format_exc(),) + return _make_failed_test( + name, exception, suiteClass, message) + +def _make_failed_test(methodname, exception, suiteClass, message): + test = _FailedTest(methodname, exception) + return suiteClass((test,)), message + +def _make_skipped_test(methodname, exception, suiteClass): + @case.skip(str(exception)) + def testSkipped(self): + pass + attrs = {methodname: testSkipped} + TestClass = type("ModuleSkipped", (case.TestCase,), attrs) + return suiteClass((TestClass(methodname),)) + +def _jython_aware_splitext(path): + if path.lower().endswith('$py.class'): + return path[:-9] + return os.path.splitext(path)[0] + + +class TestLoader(object): + """ + This class is responsible for loading tests according to various criteria + and returning them wrapped in a TestSuite + """ + testMethodPrefix = 'test' + sortTestMethodsUsing = staticmethod(util.three_way_cmp) + testNamePatterns = None + suiteClass = suite.TestSuite + _top_level_dir = None + + def __init__(self): + super(TestLoader, self).__init__() + self.errors = [] + # Tracks packages which we have called into via load_tests, to + # avoid infinite re-entrancy. + self._loading_packages = set() + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all test cases contained in testCaseClass""" + if issubclass(testCaseClass, suite.TestSuite): + raise TypeError("Test cases should not be derived from " + "TestSuite. Maybe you meant to derive from " + "TestCase?") + testCaseNames = self.getTestCaseNames(testCaseClass) + if not testCaseNames and hasattr(testCaseClass, 'runTest'): + testCaseNames = ['runTest'] + loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) + return loaded_suite + + # XXX After Python 3.5, remove backward compatibility hacks for + # use_load_tests deprecation via *args and **kws. See issue 16662. + def loadTestsFromModule(self, module, *args, pattern=None, **kws): + """Return a suite of all test cases contained in the given module""" + # This method used to take an undocumented and unofficial + # use_load_tests argument. For backward compatibility, we still + # accept the argument (which can also be the first position) but we + # ignore it and issue a deprecation warning if it's present. + if len(args) > 0 or 'use_load_tests' in kws: + warnings.warn('use_load_tests is deprecated and ignored', + DeprecationWarning) + kws.pop('use_load_tests', None) + if len(args) > 1: + # Complain about the number of arguments, but don't forget the + # required `module` argument. + complaint = len(args) + 1 + raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint)) + if len(kws) != 0: + # Since the keyword arguments are unsorted (see PEP 468), just + # pick the alphabetically sorted first argument to complain about, + # if multiple were given. At least the error message will be + # predictable. + complaint = sorted(kws)[0] + raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint)) + tests = [] + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, case.TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + + load_tests = getattr(module, 'load_tests', None) + tests = self.suiteClass(tests) + if load_tests is not None: + try: + return load_tests(self, tests, pattern) + except Exception as e: + error_case, error_message = _make_failed_load_tests( + module.__name__, e, self.suiteClass) + self.errors.append(error_message) + return error_case + return tests + + def loadTestsFromName(self, name, module=None): + """Return a suite of all test cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = name.split('.') + error_case, error_message = None, None + if module is None: + parts_copy = parts[:] + while parts_copy: + try: + module_name = '.'.join(parts_copy) + module = __import__(module_name) + break + except ImportError: + next_attribute = parts_copy.pop() + # Last error so we can give it to the user if needed. + error_case, error_message = _make_failed_import_test( + next_attribute, self.suiteClass) + if not parts_copy: + # Even the top level import failed: report that error. + self.errors.append(error_message) + return error_case + parts = parts[1:] + obj = module + for part in parts: + try: + parent, obj = obj, getattr(obj, part) + except AttributeError as e: + # We can't traverse some part of the name. + if (getattr(obj, '__path__', None) is not None + and error_case is not None): + # This is a package (no __path__ per importlib docs), and we + # encountered an error importing something. We cannot tell + # the difference between package.WrongNameTestClass and + # package.wrong_module_name so we just report the + # ImportError - it is more informative. + self.errors.append(error_message) + return error_case + else: + # Otherwise, we signal that an AttributeError has occurred. + error_case, error_message = _make_failed_test( + part, e, self.suiteClass, + 'Failed to access attribute:\n%s' % ( + traceback.format_exc(),)) + self.errors.append(error_message) + return error_case + + if isinstance(obj, types.ModuleType): + return self.loadTestsFromModule(obj) + elif isinstance(obj, type) and issubclass(obj, case.TestCase): + return self.loadTestsFromTestCase(obj) + elif (isinstance(obj, types.FunctionType) and + isinstance(parent, type) and + issubclass(parent, case.TestCase)): + name = parts[-1] + inst = parent(name) + # static methods follow a different path + if not isinstance(getattr(inst, name), types.FunctionType): + return self.suiteClass([inst]) + elif isinstance(obj, suite.TestSuite): + return obj + if callable(obj): + test = obj() + if isinstance(test, suite.TestSuite): + return test + elif isinstance(test, case.TestCase): + return self.suiteClass([test]) + else: + raise TypeError("calling %s returned %s, not a test" % + (obj, test)) + else: + raise TypeError("don't know how to make test from: %s" % obj) + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all test cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [self.loadTestsFromName(name, module) for name in names] + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass + """ + def shouldIncludeMethod(attrname): + if not attrname.startswith(self.testMethodPrefix): + return False + testFunc = getattr(testCaseClass, attrname) + if not callable(testFunc): + return False + fullName = f'%s.%s.%s' % ( + testCaseClass.__module__, testCaseClass.__qualname__, attrname + ) + return self.testNamePatterns is None or \ + any(fnmatchcase(fullName, pattern) for pattern in self.testNamePatterns) + testFnNames = list(filter(shouldIncludeMethod, dir(testCaseClass))) + if self.sortTestMethodsUsing: + testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing)) + return testFnNames + + def discover(self, start_dir, pattern='test*.py', top_level_dir=None): + """Find and return all test modules from the specified start + directory, recursing into subdirectories to find them and return all + tests found within them. Only test files that match the pattern will + be loaded. (Using shell style pattern matching.) + + All test modules must be importable from the top level of the project. + If the start directory is not the top level directory then the top + level directory must be specified separately. + + If a test package name (directory with '__init__.py') matches the + pattern then the package will be checked for a 'load_tests' function. If + this exists then it will be called with (loader, tests, pattern) unless + the package has already had load_tests called from the same discovery + invocation, in which case the package module object is not scanned for + tests - this ensures that when a package uses discover to further + discover child tests that infinite recursion does not happen. + + If load_tests exists then discovery does *not* recurse into the package, + load_tests is responsible for loading all tests in the package. + + The pattern is deliberately not stored as a loader attribute so that + packages can continue discovery themselves. top_level_dir is stored so + load_tests does not need to pass this argument in to loader.discover(). + + Paths are sorted before being imported to ensure reproducible execution + order even on filesystems with non-alphabetical ordering like ext3/4. + """ + set_implicit_top = False + if top_level_dir is None and self._top_level_dir is not None: + # make top_level_dir optional if called from load_tests in a package + top_level_dir = self._top_level_dir + elif top_level_dir is None: + set_implicit_top = True + top_level_dir = start_dir + + top_level_dir = os.path.abspath(top_level_dir) + + if not top_level_dir in sys.path: + # all test modules must be importable from the top level directory + # should we *unconditionally* put the start directory in first + # in sys.path to minimise likelihood of conflicts between installed + # modules and development versions? + sys.path.insert(0, top_level_dir) + self._top_level_dir = top_level_dir + + is_not_importable = False + is_namespace = False + tests = [] + if os.path.isdir(os.path.abspath(start_dir)): + start_dir = os.path.abspath(start_dir) + if start_dir != top_level_dir: + is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py')) + else: + # support for discovery from dotted module names + try: + __import__(start_dir) + except ImportError: + is_not_importable = True + else: + the_module = sys.modules[start_dir] + top_part = start_dir.split('.')[0] + try: + start_dir = os.path.abspath( + os.path.dirname((the_module.__file__))) + except AttributeError: + # look for namespace packages + try: + spec = the_module.__spec__ + except AttributeError: + spec = None + + if spec and spec.loader is None: + if spec.submodule_search_locations is not None: + is_namespace = True + + for path in the_module.__path__: + if (not set_implicit_top and + not path.startswith(top_level_dir)): + continue + self._top_level_dir = \ + (path.split(the_module.__name__ + .replace(".", os.path.sep))[0]) + tests.extend(self._find_tests(path, + pattern, + namespace=True)) + elif the_module.__name__ in sys.builtin_module_names: + # builtin module + raise TypeError('Can not use builtin modules ' + 'as dotted module names') from None + else: + raise TypeError( + 'don\'t know how to discover from {!r}' + .format(the_module)) from None + + if set_implicit_top: + if not is_namespace: + self._top_level_dir = \ + self._get_directory_containing_module(top_part) + sys.path.remove(top_level_dir) + else: + sys.path.remove(top_level_dir) + + if is_not_importable: + raise ImportError('Start directory is not importable: %r' % start_dir) + + if not is_namespace: + tests = list(self._find_tests(start_dir, pattern)) + return self.suiteClass(tests) + + def _get_directory_containing_module(self, module_name): + module = sys.modules[module_name] + full_path = os.path.abspath(module.__file__) + + if os.path.basename(full_path).lower().startswith('__init__.py'): + return os.path.dirname(os.path.dirname(full_path)) + else: + # here we have been given a module rather than a package - so + # all we can do is search the *same* directory the module is in + # should an exception be raised instead + return os.path.dirname(full_path) + + def _get_name_from_path(self, path): + if path == self._top_level_dir: + return '.' + path = _jython_aware_splitext(os.path.normpath(path)) + + _relpath = os.path.relpath(path, self._top_level_dir) + assert not os.path.isabs(_relpath), "Path must be within the project" + assert not _relpath.startswith('..'), "Path must be within the project" + + name = _relpath.replace(os.path.sep, '.') + return name + + def _get_module_from_name(self, name): + __import__(name) + return sys.modules[name] + + def _match_path(self, path, full_path, pattern): + # override this method to use alternative matching strategy + return fnmatch(path, pattern) + + def _find_tests(self, start_dir, pattern, namespace=False): + """Used by discovery. Yields test suites it loads.""" + # Handle the __init__ in this package + name = self._get_name_from_path(start_dir) + # name is '.' when start_dir == top_level_dir (and top_level_dir is by + # definition not a package). + if name != '.' and name not in self._loading_packages: + # name is in self._loading_packages while we have called into + # loadTestsFromModule with name. + tests, should_recurse = self._find_test_path( + start_dir, pattern, namespace) + if tests is not None: + yield tests + if not should_recurse: + # Either an error occurred, or load_tests was used by the + # package. + return + # Handle the contents. + paths = sorted(os.listdir(start_dir)) + for path in paths: + full_path = os.path.join(start_dir, path) + tests, should_recurse = self._find_test_path( + full_path, pattern, namespace) + if tests is not None: + yield tests + if should_recurse: + # we found a package that didn't use load_tests. + name = self._get_name_from_path(full_path) + self._loading_packages.add(name) + try: + yield from self._find_tests(full_path, pattern, namespace) + finally: + self._loading_packages.discard(name) + + def _find_test_path(self, full_path, pattern, namespace=False): + """Used by discovery. + + Loads tests from a single file, or a directories' __init__.py when + passed the directory. + + Returns a tuple (None_or_tests_from_file, should_recurse). + """ + basename = os.path.basename(full_path) + if os.path.isfile(full_path): + if not VALID_MODULE_NAME.match(basename): + # valid Python identifiers only + return None, False + if not self._match_path(basename, full_path, pattern): + return None, False + # if the test file matches, load it + name = self._get_name_from_path(full_path) + try: + module = self._get_module_from_name(name) + except case.SkipTest as e: + return _make_skipped_test(name, e, self.suiteClass), False + except: + error_case, error_message = \ + _make_failed_import_test(name, self.suiteClass) + self.errors.append(error_message) + return error_case, False + else: + mod_file = os.path.abspath( + getattr(module, '__file__', full_path)) + realpath = _jython_aware_splitext( + os.path.realpath(mod_file)) + fullpath_noext = _jython_aware_splitext( + os.path.realpath(full_path)) + if realpath.lower() != fullpath_noext.lower(): + module_dir = os.path.dirname(realpath) + mod_name = _jython_aware_splitext( + os.path.basename(full_path)) + expected_dir = os.path.dirname(full_path) + msg = ("%r module incorrectly imported from %r. Expected " + "%r. Is this module globally installed?") + raise ImportError( + msg % (mod_name, module_dir, expected_dir)) + return self.loadTestsFromModule(module, pattern=pattern), False + elif os.path.isdir(full_path): + if (not namespace and + not os.path.isfile(os.path.join(full_path, '__init__.py'))): + return None, False + + load_tests = None + tests = None + name = self._get_name_from_path(full_path) + try: + package = self._get_module_from_name(name) + except case.SkipTest as e: + return _make_skipped_test(name, e, self.suiteClass), False + except: + error_case, error_message = \ + _make_failed_import_test(name, self.suiteClass) + self.errors.append(error_message) + return error_case, False + else: + load_tests = getattr(package, 'load_tests', None) + # Mark this package as being in load_tests (possibly ;)) + self._loading_packages.add(name) + try: + tests = self.loadTestsFromModule(package, pattern=pattern) + if load_tests is not None: + # loadTestsFromModule(package) has loaded tests for us. + return tests, False + return tests, True + finally: + self._loading_packages.discard(name) + else: + return None, False + + +defaultTestLoader = TestLoader() + + +def _makeLoader(prefix, sortUsing, suiteClass=None, testNamePatterns=None): + loader = TestLoader() + loader.sortTestMethodsUsing = sortUsing + loader.testMethodPrefix = prefix + loader.testNamePatterns = testNamePatterns + if suiteClass: + loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix, sortUsing=util.three_way_cmp, testNamePatterns=None): + return _makeLoader(prefix, sortUsing, testNamePatterns=testNamePatterns).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase( + testCaseClass) + +def findTestCases(module, prefix='test', sortUsing=util.three_way_cmp, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(\ + module) diff --git a/Monika After Story/game/python-packages/unittest/main.py b/Monika After Story/game/python-packages/unittest/main.py new file mode 100644 index 0000000000..e62469aa2a --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/main.py @@ -0,0 +1,275 @@ +"""Unittest main program""" + +import sys +import argparse +import os + +from . import loader, runner +from .signals import installHandler + +__unittest = True + +MAIN_EXAMPLES = """\ +Examples: + %(prog)s test_module - run tests from test_module + %(prog)s module.TestClass - run tests from module.TestClass + %(prog)s module.Class.test_method - run specified test method + %(prog)s path/to/test_file.py - run tests from test_file.py +""" + +MODULE_EXAMPLES = """\ +Examples: + %(prog)s - run default set of tests + %(prog)s MyTestSuite - run suite 'MyTestSuite' + %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething + %(prog)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + +def _convert_name(name): + # on Linux / Mac OS X 'foo.PY' is not importable, but on + # Windows it is. Simpler to do a case insensitive match + # a better check would be to check that the name is a + # valid Python module name. + if os.path.isfile(name) and name.lower().endswith('.py'): + if os.path.isabs(name): + rel_path = os.path.relpath(name, os.getcwd()) + if os.path.isabs(rel_path) or rel_path.startswith(os.pardir): + return name + name = rel_path + # on Windows both '\' and '/' are used as path + # separators. Better to replace both than rely on os.path.sep + return name[:-3].replace('\\', '.').replace('/', '.') + return name + +def _convert_names(names): + return [_convert_name(name) for name in names] + + +def _convert_select_pattern(pattern): + if not '*' in pattern: + pattern = '*%s*' % pattern + return pattern + + +class TestProgram(object): + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + # defaults for testing + module=None + verbosity = 1 + failfast = catchbreak = buffer = progName = warnings = testNamePatterns = None + _discovery_parser = None + + def __init__(self, module='__main__', defaultTest=None, argv=None, + testRunner=None, testLoader=loader.defaultTestLoader, + exit=True, verbosity=1, failfast=None, catchbreak=None, + buffer=None, warnings=None, *, tb_locals=False): + if isinstance(module, str): + self.module = __import__(module) + for part in module.split('.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + + self.exit = exit + self.failfast = failfast + self.catchbreak = catchbreak + self.verbosity = verbosity + self.buffer = buffer + self.tb_locals = tb_locals + if warnings is None and not sys.warnoptions: + # even if DeprecationWarnings are ignored by default + # print them anyway unless other warnings settings are + # specified by the warnings arg or the -W python flag + self.warnings = 'default' + else: + # here self.warnings is set either to the value passed + # to the warnings args or to None. + # If the user didn't pass a value self.warnings will + # be None. This means that the behavior is unchanged + # and depends on the values passed to -W. + self.warnings = warnings + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: + print(msg) + if self._discovery_parser is None: + self._initArgParsers() + self._print_help() + sys.exit(2) + + def _print_help(self, *args, **kwargs): + if self.module is None: + print(self._main_parser.format_help()) + print(MAIN_EXAMPLES % {'prog': self.progName}) + self._discovery_parser.print_help() + else: + print(self._main_parser.format_help()) + print(MODULE_EXAMPLES % {'prog': self.progName}) + + def parseArgs(self, argv): + self._initArgParsers() + if self.module is None: + if len(argv) > 1 and argv[1].lower() == 'discover': + self._do_discovery(argv[2:]) + return + self._main_parser.parse_args(argv[1:], self) + if not self.tests: + # this allows "python -m unittest -v" to still work for + # test discovery. + self._do_discovery([]) + return + else: + self._main_parser.parse_args(argv[1:], self) + + if self.tests: + self.testNames = _convert_names(self.tests) + if __name__ == '__main__': + # to support python -m unittest ... + self.module = None + elif self.defaultTest is None: + # createTests will load tests from self.module + self.testNames = None + elif isinstance(self.defaultTest, str): + self.testNames = (self.defaultTest,) + else: + self.testNames = list(self.defaultTest) + self.createTests() + + def createTests(self, from_discovery=False, Loader=None): + if self.testNamePatterns: + self.testLoader.testNamePatterns = self.testNamePatterns + if from_discovery: + loader = self.testLoader if Loader is None else Loader() + self.test = loader.discover(self.start, self.pattern, self.top) + elif self.testNames is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + else: + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def _initArgParsers(self): + parent_parser = self._getParentArgParser() + self._main_parser = self._getMainArgParser(parent_parser) + self._discovery_parser = self._getDiscoveryArgParser(parent_parser) + + def _getParentArgParser(self): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument('-v', '--verbose', dest='verbosity', + action='store_const', const=2, + help='Verbose output') + parser.add_argument('-q', '--quiet', dest='verbosity', + action='store_const', const=0, + help='Quiet output') + parser.add_argument('--locals', dest='tb_locals', + action='store_true', + help='Show local variables in tracebacks') + if self.failfast is None: + parser.add_argument('-f', '--failfast', dest='failfast', + action='store_true', + help='Stop on first fail or error') + self.failfast = False + if self.catchbreak is None: + parser.add_argument('-c', '--catch', dest='catchbreak', + action='store_true', + help='Catch Ctrl-C and display results so far') + self.catchbreak = False + if self.buffer is None: + parser.add_argument('-b', '--buffer', dest='buffer', + action='store_true', + help='Buffer stdout and stderr during tests') + self.buffer = False + if self.testNamePatterns is None: + parser.add_argument('-k', dest='testNamePatterns', + action='append', type=_convert_select_pattern, + help='Only run tests which match the given substring') + self.testNamePatterns = [] + + return parser + + def _getMainArgParser(self, parent): + parser = argparse.ArgumentParser(parents=[parent]) + parser.prog = self.progName + parser.print_help = self._print_help + + parser.add_argument('tests', nargs='*', + help='a list of any number of test modules, ' + 'classes and test methods.') + + return parser + + def _getDiscoveryArgParser(self, parent): + parser = argparse.ArgumentParser(parents=[parent]) + parser.prog = '%s discover' % self.progName + parser.epilog = ('For test discovery all test modules must be ' + 'importable from the top level directory of the ' + 'project.') + + parser.add_argument('-s', '--start-directory', dest='start', + help="Directory to start discovery ('.' default)") + parser.add_argument('-p', '--pattern', dest='pattern', + help="Pattern to match tests ('test*.py' default)") + parser.add_argument('-t', '--top-level-directory', dest='top', + help='Top level directory of project (defaults to ' + 'start directory)') + for arg in ('start', 'pattern', 'top'): + parser.add_argument(arg, nargs='?', + default=argparse.SUPPRESS, + help=argparse.SUPPRESS) + + return parser + + def _do_discovery(self, argv, Loader=None): + self.start = '.' + self.pattern = 'test*.py' + self.top = None + if argv is not None: + # handle command line args for test discovery + if self._discovery_parser is None: + # for testing + self._initArgParsers() + self._discovery_parser.parse_args(argv, self) + + self.createTests(from_discovery=True, Loader=Loader) + + def runTests(self): + if self.catchbreak: + installHandler() + if self.testRunner is None: + self.testRunner = runner.TextTestRunner + if isinstance(self.testRunner, type): + try: + try: + testRunner = self.testRunner(verbosity=self.verbosity, + failfast=self.failfast, + buffer=self.buffer, + warnings=self.warnings, + tb_locals=self.tb_locals) + except TypeError: + # didn't accept the tb_locals argument + testRunner = self.testRunner(verbosity=self.verbosity, + failfast=self.failfast, + buffer=self.buffer, + warnings=self.warnings) + except TypeError: + # didn't accept the verbosity, buffer or failfast arguments + testRunner = self.testRunner() + else: + # it is assumed to be a TestRunner instance + testRunner = self.testRunner + self.result = testRunner.run(self.test) + if self.exit: + sys.exit(not self.result.wasSuccessful()) + +main = TestProgram diff --git a/Monika After Story/game/python-packages/unittest/mock.py b/Monika After Story/game/python-packages/unittest/mock.py new file mode 100644 index 0000000000..f03c88baca --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/mock.py @@ -0,0 +1,2891 @@ +# mock.py +# Test tools for mocking and patching. +# Maintained by Michael Foord +# Backport for other versions of Python available from +# https://pypi.org/project/mock + +__all__ = ( + 'Mock', + 'MagicMock', + 'patch', + 'sentinel', + 'DEFAULT', + 'ANY', + 'call', + 'create_autospec', + 'AsyncMock', + 'FILTER_DIR', + 'NonCallableMock', + 'NonCallableMagicMock', + 'mock_open', + 'PropertyMock', + 'seal', +) + + +import asyncio +import contextlib +import io +import inspect +import pprint +import sys +import builtins +from asyncio import iscoroutinefunction +from types import CodeType, ModuleType, MethodType +from unittest.util import safe_repr +from functools import wraps, partial + + +_builtins = {name for name in dir(builtins) if not name.startswith('_')} + +FILTER_DIR = True + +# Workaround for issue #12370 +# Without this, the __class__ properties wouldn't be set correctly +_safe_super = super + +def _is_async_obj(obj): + if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): + return False + if hasattr(obj, '__func__'): + obj = getattr(obj, '__func__') + return iscoroutinefunction(obj) or inspect.isawaitable(obj) + + +def _is_async_func(func): + if getattr(func, '__code__', None): + return iscoroutinefunction(func) + else: + return False + + +def _is_instance_mock(obj): + # can't use isinstance on Mock objects because they override __class__ + # The base class for all mocks is NonCallableMock + return issubclass(type(obj), NonCallableMock) + + +def _is_exception(obj): + return ( + isinstance(obj, BaseException) or + isinstance(obj, type) and issubclass(obj, BaseException) + ) + + +def _extract_mock(obj): + # Autospecced functions will return a FunctionType with "mock" attribute + # which is the actual mock object that needs to be used. + if isinstance(obj, FunctionTypes) and hasattr(obj, 'mock'): + return obj.mock + else: + return obj + + +def _get_signature_object(func, as_instance, eat_self): + """ + Given an arbitrary, possibly callable object, try to create a suitable + signature object. + Return a (reduced func, signature) tuple, or None. + """ + if isinstance(func, type) and not as_instance: + # If it's a type and should be modelled as a type, use __init__. + func = func.__init__ + # Skip the `self` argument in __init__ + eat_self = True + elif not isinstance(func, FunctionTypes): + # If we really want to model an instance of the passed type, + # __call__ should be looked up, not __init__. + try: + func = func.__call__ + except AttributeError: + return None + if eat_self: + sig_func = partial(func, None) + else: + sig_func = func + try: + return func, inspect.signature(sig_func) + except ValueError: + # Certain callable types are not supported by inspect.signature() + return None + + +def _check_signature(func, mock, skipfirst, instance=False): + sig = _get_signature_object(func, instance, skipfirst) + if sig is None: + return + func, sig = sig + def checksig(self, /, *args, **kwargs): + sig.bind(*args, **kwargs) + _copy_func_details(func, checksig) + type(mock)._mock_check_sig = checksig + type(mock).__signature__ = sig + + +def _copy_func_details(func, funcopy): + # we explicitly don't copy func.__dict__ into this copy as it would + # expose original attributes that should be mocked + for attribute in ( + '__name__', '__doc__', '__text_signature__', + '__module__', '__defaults__', '__kwdefaults__', + ): + try: + setattr(funcopy, attribute, getattr(func, attribute)) + except AttributeError: + pass + + +def _callable(obj): + if isinstance(obj, type): + return True + if isinstance(obj, (staticmethod, classmethod, MethodType)): + return _callable(obj.__func__) + if getattr(obj, '__call__', None) is not None: + return True + return False + + +def _is_list(obj): + # checks for list or tuples + # XXXX badly named! + return type(obj) in (list, tuple) + + +def _instance_callable(obj): + """Given an object, return True if the object is callable. + For classes, return True if instances would be callable.""" + if not isinstance(obj, type): + # already an instance + return getattr(obj, '__call__', None) is not None + + # *could* be broken by a class overriding __mro__ or __dict__ via + # a metaclass + for base in (obj,) + obj.__mro__: + if base.__dict__.get('__call__') is not None: + return True + return False + + +def _set_signature(mock, original, instance=False): + # creates a function with signature (*args, **kwargs) that delegates to a + # mock. It still does signature checking by calling a lambda with the same + # signature as the original. + + skipfirst = isinstance(original, type) + result = _get_signature_object(original, instance, skipfirst) + if result is None: + return mock + func, sig = result + def checksig(*args, **kwargs): + sig.bind(*args, **kwargs) + _copy_func_details(func, checksig) + + name = original.__name__ + if not name.isidentifier(): + name = 'funcopy' + context = {'_checksig_': checksig, 'mock': mock} + src = """def %s(*args, **kwargs): + _checksig_(*args, **kwargs) + return mock(*args, **kwargs)""" % name + exec (src, context) + funcopy = context[name] + _setup_func(funcopy, mock, sig) + return funcopy + + +def _setup_func(funcopy, mock, sig): + funcopy.mock = mock + + def assert_called_with(*args, **kwargs): + return mock.assert_called_with(*args, **kwargs) + def assert_called(*args, **kwargs): + return mock.assert_called(*args, **kwargs) + def assert_not_called(*args, **kwargs): + return mock.assert_not_called(*args, **kwargs) + def assert_called_once(*args, **kwargs): + return mock.assert_called_once(*args, **kwargs) + def assert_called_once_with(*args, **kwargs): + return mock.assert_called_once_with(*args, **kwargs) + def assert_has_calls(*args, **kwargs): + return mock.assert_has_calls(*args, **kwargs) + def assert_any_call(*args, **kwargs): + return mock.assert_any_call(*args, **kwargs) + def reset_mock(): + funcopy.method_calls = _CallList() + funcopy.mock_calls = _CallList() + mock.reset_mock() + ret = funcopy.return_value + if _is_instance_mock(ret) and not ret is mock: + ret.reset_mock() + + funcopy.called = False + funcopy.call_count = 0 + funcopy.call_args = None + funcopy.call_args_list = _CallList() + funcopy.method_calls = _CallList() + funcopy.mock_calls = _CallList() + + funcopy.return_value = mock.return_value + funcopy.side_effect = mock.side_effect + funcopy._mock_children = mock._mock_children + + funcopy.assert_called_with = assert_called_with + funcopy.assert_called_once_with = assert_called_once_with + funcopy.assert_has_calls = assert_has_calls + funcopy.assert_any_call = assert_any_call + funcopy.reset_mock = reset_mock + funcopy.assert_called = assert_called + funcopy.assert_not_called = assert_not_called + funcopy.assert_called_once = assert_called_once + funcopy.__signature__ = sig + + mock._mock_delegate = funcopy + + +def _setup_async_mock(mock): + mock._is_coroutine = asyncio.coroutines._is_coroutine + mock.await_count = 0 + mock.await_args = None + mock.await_args_list = _CallList() + + # Mock is not configured yet so the attributes are set + # to a function and then the corresponding mock helper function + # is called when the helper is accessed similar to _setup_func. + def wrapper(attr, /, *args, **kwargs): + return getattr(mock.mock, attr)(*args, **kwargs) + + for attribute in ('assert_awaited', + 'assert_awaited_once', + 'assert_awaited_with', + 'assert_awaited_once_with', + 'assert_any_await', + 'assert_has_awaits', + 'assert_not_awaited'): + + # setattr(mock, attribute, wrapper) causes late binding + # hence attribute will always be the last value in the loop + # Use partial(wrapper, attribute) to ensure the attribute is bound + # correctly. + setattr(mock, attribute, partial(wrapper, attribute)) + + +def _is_magic(name): + return '__%s__' % name[2:-2] == name + + +class _SentinelObject(object): + "A unique, named, sentinel object." + def __init__(self, name): + self.name = name + + def __repr__(self): + return 'sentinel.%s' % self.name + + def __reduce__(self): + return 'sentinel.%s' % self.name + + +class _Sentinel(object): + """Access attributes to return a named object, usable as a sentinel.""" + def __init__(self): + self._sentinels = {} + + def __getattr__(self, name): + if name == '__bases__': + # Without this help(unittest.mock) raises an exception + raise AttributeError + return self._sentinels.setdefault(name, _SentinelObject(name)) + + def __reduce__(self): + return 'sentinel' + + +sentinel = _Sentinel() + +DEFAULT = sentinel.DEFAULT +_missing = sentinel.MISSING +_deleted = sentinel.DELETED + + +_allowed_names = { + 'return_value', '_mock_return_value', 'side_effect', + '_mock_side_effect', '_mock_parent', '_mock_new_parent', + '_mock_name', '_mock_new_name' +} + + +def _delegating_property(name): + _allowed_names.add(name) + _the_name = '_mock_' + name + def _get(self, name=name, _the_name=_the_name): + sig = self._mock_delegate + if sig is None: + return getattr(self, _the_name) + return getattr(sig, name) + def _set(self, value, name=name, _the_name=_the_name): + sig = self._mock_delegate + if sig is None: + self.__dict__[_the_name] = value + else: + setattr(sig, name, value) + + return property(_get, _set) + + + +class _CallList(list): + + def __contains__(self, value): + if not isinstance(value, list): + return list.__contains__(self, value) + len_value = len(value) + len_self = len(self) + if len_value > len_self: + return False + + for i in range(0, len_self - len_value + 1): + sub_list = self[i:i+len_value] + if sub_list == value: + return True + return False + + def __repr__(self): + return pprint.pformat(list(self)) + + +def _check_and_set_parent(parent, value, name, new_name): + value = _extract_mock(value) + + if not _is_instance_mock(value): + return False + if ((value._mock_name or value._mock_new_name) or + (value._mock_parent is not None) or + (value._mock_new_parent is not None)): + return False + + _parent = parent + while _parent is not None: + # setting a mock (value) as a child or return value of itself + # should not modify the mock + if _parent is value: + return False + _parent = _parent._mock_new_parent + + if new_name: + value._mock_new_parent = parent + value._mock_new_name = new_name + if name: + value._mock_parent = parent + value._mock_name = name + return True + +# Internal class to identify if we wrapped an iterator object or not. +class _MockIter(object): + def __init__(self, obj): + self.obj = iter(obj) + def __next__(self): + return next(self.obj) + +class Base(object): + _mock_return_value = DEFAULT + _mock_side_effect = None + def __init__(self, /, *args, **kwargs): + pass + + + +class NonCallableMock(Base): + """A non-callable version of `Mock`""" + + def __new__(cls, /, *args, **kw): + # every instance has its own class + # so we can create magic methods on the + # class without stomping on other mocks + bases = (cls,) + if not issubclass(cls, AsyncMockMixin): + # Check if spec is an async object or function + bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments + spec_arg = bound_args.get('spec_set', bound_args.get('spec')) + if spec_arg is not None and _is_async_obj(spec_arg): + bases = (AsyncMockMixin, cls) + new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) + instance = _safe_super(NonCallableMock, cls).__new__(new) + return instance + + + def __init__( + self, spec=None, wraps=None, name=None, spec_set=None, + parent=None, _spec_state=None, _new_name='', _new_parent=None, + _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs + ): + if _new_parent is None: + _new_parent = parent + + __dict__ = self.__dict__ + __dict__['_mock_parent'] = parent + __dict__['_mock_name'] = name + __dict__['_mock_new_name'] = _new_name + __dict__['_mock_new_parent'] = _new_parent + __dict__['_mock_sealed'] = False + + if spec_set is not None: + spec = spec_set + spec_set = True + if _eat_self is None: + _eat_self = parent is not None + + self._mock_add_spec(spec, spec_set, _spec_as_instance, _eat_self) + + __dict__['_mock_children'] = {} + __dict__['_mock_wraps'] = wraps + __dict__['_mock_delegate'] = None + + __dict__['_mock_called'] = False + __dict__['_mock_call_args'] = None + __dict__['_mock_call_count'] = 0 + __dict__['_mock_call_args_list'] = _CallList() + __dict__['_mock_mock_calls'] = _CallList() + + __dict__['method_calls'] = _CallList() + __dict__['_mock_unsafe'] = unsafe + + if kwargs: + self.configure_mock(**kwargs) + + _safe_super(NonCallableMock, self).__init__( + spec, wraps, name, spec_set, parent, + _spec_state + ) + + + def attach_mock(self, mock, attribute): + """ + Attach a mock as an attribute of this one, replacing its name and + parent. Calls to the attached mock will be recorded in the + `method_calls` and `mock_calls` attributes of this one.""" + inner_mock = _extract_mock(mock) + + inner_mock._mock_parent = None + inner_mock._mock_new_parent = None + inner_mock._mock_name = '' + inner_mock._mock_new_name = None + + setattr(self, attribute, mock) + + + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + + + def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, + _eat_self=False): + _spec_class = None + _spec_signature = None + _spec_asyncs = [] + + for attr in dir(spec): + if iscoroutinefunction(getattr(spec, attr, None)): + _spec_asyncs.append(attr) + + if spec is not None and not _is_list(spec): + if isinstance(spec, type): + _spec_class = spec + else: + _spec_class = type(spec) + res = _get_signature_object(spec, + _spec_as_instance, _eat_self) + _spec_signature = res and res[1] + + spec = dir(spec) + + __dict__ = self.__dict__ + __dict__['_spec_class'] = _spec_class + __dict__['_spec_set'] = spec_set + __dict__['_spec_signature'] = _spec_signature + __dict__['_mock_methods'] = spec + __dict__['_spec_asyncs'] = _spec_asyncs + + def __get_return_value(self): + ret = self._mock_return_value + if self._mock_delegate is not None: + ret = self._mock_delegate.return_value + + if ret is DEFAULT: + ret = self._get_child_mock( + _new_parent=self, _new_name='()' + ) + self.return_value = ret + return ret + + + def __set_return_value(self, value): + if self._mock_delegate is not None: + self._mock_delegate.return_value = value + else: + self._mock_return_value = value + _check_and_set_parent(self, value, None, '()') + + __return_value_doc = "The value to be returned when the mock is called." + return_value = property(__get_return_value, __set_return_value, + __return_value_doc) + + + @property + def __class__(self): + if self._spec_class is None: + return type(self) + return self._spec_class + + called = _delegating_property('called') + call_count = _delegating_property('call_count') + call_args = _delegating_property('call_args') + call_args_list = _delegating_property('call_args_list') + mock_calls = _delegating_property('mock_calls') + + + def __get_side_effect(self): + delegated = self._mock_delegate + if delegated is None: + return self._mock_side_effect + sf = delegated.side_effect + if (sf is not None and not callable(sf) + and not isinstance(sf, _MockIter) and not _is_exception(sf)): + sf = _MockIter(sf) + delegated.side_effect = sf + return sf + + def __set_side_effect(self, value): + value = _try_iter(value) + delegated = self._mock_delegate + if delegated is None: + self._mock_side_effect = value + else: + delegated.side_effect = value + + side_effect = property(__get_side_effect, __set_side_effect) + + + def reset_mock(self, visited=None,*, return_value=False, side_effect=False): + "Restore the mock object to its initial state." + if visited is None: + visited = [] + if id(self) in visited: + return + visited.append(id(self)) + + self.called = False + self.call_args = None + self.call_count = 0 + self.mock_calls = _CallList() + self.call_args_list = _CallList() + self.method_calls = _CallList() + + if return_value: + self._mock_return_value = DEFAULT + if side_effect: + self._mock_side_effect = None + + for child in self._mock_children.values(): + if isinstance(child, _SpecState) or child is _deleted: + continue + child.reset_mock(visited, return_value=return_value, side_effect=side_effect) + + ret = self._mock_return_value + if _is_instance_mock(ret) and ret is not self: + ret.reset_mock(visited) + + + def configure_mock(self, /, **kwargs): + """Set attributes on the mock through keyword arguments. + + Attributes plus return values and side effects can be set on child + mocks using standard dot notation and unpacking a dictionary in the + method call: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock.configure_mock(**attrs)""" + for arg, val in sorted(kwargs.items(), + # we sort on the number of dots so that + # attributes are set before we set attributes on + # attributes + key=lambda entry: entry[0].count('.')): + args = arg.split('.') + final = args.pop() + obj = self + for entry in args: + obj = getattr(obj, entry) + setattr(obj, final, val) + + + def __getattr__(self, name): + if name in {'_mock_methods', '_mock_unsafe'}: + raise AttributeError(name) + elif self._mock_methods is not None: + if name not in self._mock_methods or name in _all_magics: + raise AttributeError("Mock object has no attribute %r" % name) + elif _is_magic(name): + raise AttributeError(name) + if not self._mock_unsafe: + if name.startswith(('assert', 'assret')): + raise AttributeError("Attributes cannot start with 'assert' " + "or 'assret'") + + result = self._mock_children.get(name) + if result is _deleted: + raise AttributeError(name) + elif result is None: + wraps = None + if self._mock_wraps is not None: + # XXXX should we get the attribute without triggering code + # execution? + wraps = getattr(self._mock_wraps, name) + + result = self._get_child_mock( + parent=self, name=name, wraps=wraps, _new_name=name, + _new_parent=self + ) + self._mock_children[name] = result + + elif isinstance(result, _SpecState): + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + self._mock_children[name] = result + + return result + + + def _extract_mock_name(self): + _name_list = [self._mock_new_name] + _parent = self._mock_new_parent + last = self + + dot = '.' + if _name_list == ['()']: + dot = '' + + while _parent is not None: + last = _parent + + _name_list.append(_parent._mock_new_name + dot) + dot = '.' + if _parent._mock_new_name == '()': + dot = '' + + _parent = _parent._mock_new_parent + + _name_list = list(reversed(_name_list)) + _first = last._mock_name or 'mock' + if len(_name_list) > 1: + if _name_list[1] not in ('()', '().'): + _first += '.' + _name_list[0] = _first + return ''.join(_name_list) + + def __repr__(self): + name = self._extract_mock_name() + + name_string = '' + if name not in ('mock', 'mock.'): + name_string = ' name=%r' % name + + spec_string = '' + if self._spec_class is not None: + spec_string = ' spec=%r' + if self._spec_set: + spec_string = ' spec_set=%r' + spec_string = spec_string % self._spec_class.__name__ + return "<%s%s%s id='%s'>" % ( + type(self).__name__, + name_string, + spec_string, + id(self) + ) + + + def __dir__(self): + """Filter the output of `dir(mock)` to only useful members.""" + if not FILTER_DIR: + return object.__dir__(self) + + extras = self._mock_methods or [] + from_type = dir(type(self)) + from_dict = list(self.__dict__) + from_child_mocks = [ + m_name for m_name, m_value in self._mock_children.items() + if m_value is not _deleted] + + from_type = [e for e in from_type if not e.startswith('_')] + from_dict = [e for e in from_dict if not e.startswith('_') or + _is_magic(e)] + return sorted(set(extras + from_type + from_dict + from_child_mocks)) + + + def __setattr__(self, name, value): + if name in _allowed_names: + # property setters go through here + return object.__setattr__(self, name, value) + elif (self._spec_set and self._mock_methods is not None and + name not in self._mock_methods and + name not in self.__dict__): + raise AttributeError("Mock object has no attribute '%s'" % name) + elif name in _unsupported_magics: + msg = 'Attempting to set unsupported magic method %r.' % name + raise AttributeError(msg) + elif name in _all_magics: + if self._mock_methods is not None and name not in self._mock_methods: + raise AttributeError("Mock object has no attribute '%s'" % name) + + if not _is_instance_mock(value): + setattr(type(self), name, _get_method(name, value)) + original = value + value = lambda *args, **kw: original(self, *args, **kw) + else: + # only set _new_name and not name so that mock_calls is tracked + # but not method calls + _check_and_set_parent(self, value, None, name) + setattr(type(self), name, value) + self._mock_children[name] = value + elif name == '__class__': + self._spec_class = value + return + else: + if _check_and_set_parent(self, value, name, name): + self._mock_children[name] = value + + if self._mock_sealed and not hasattr(self, name): + mock_name = f'{self._extract_mock_name()}.{name}' + raise AttributeError(f'Cannot set {mock_name}') + + return object.__setattr__(self, name, value) + + + def __delattr__(self, name): + if name in _all_magics and name in type(self).__dict__: + delattr(type(self), name) + if name not in self.__dict__: + # for magic methods that are still MagicProxy objects and + # not set on the instance itself + return + + obj = self._mock_children.get(name, _missing) + if name in self.__dict__: + _safe_super(NonCallableMock, self).__delattr__(name) + elif obj is _deleted: + raise AttributeError(name) + if obj is not _missing: + del self._mock_children[name] + self._mock_children[name] = _deleted + + + def _format_mock_call_signature(self, args, kwargs): + name = self._mock_name or 'mock' + return _format_call_signature(name, args, kwargs) + + + def _format_mock_failure_message(self, args, kwargs, action='call'): + message = 'expected %s not found.\nExpected: %s\nActual: %s' + expected_string = self._format_mock_call_signature(args, kwargs) + call_args = self.call_args + actual_string = self._format_mock_call_signature(*call_args) + return message % (action, expected_string, actual_string) + + + def _get_call_signature_from_name(self, name): + """ + * If call objects are asserted against a method/function like obj.meth1 + then there could be no name for the call object to lookup. Hence just + return the spec_signature of the method/function being asserted against. + * If the name is not empty then remove () and split by '.' to get + list of names to iterate through the children until a potential + match is found. A child mock is created only during attribute access + so if we get a _SpecState then no attributes of the spec were accessed + and can be safely exited. + """ + if not name: + return self._spec_signature + + sig = None + names = name.replace('()', '').split('.') + children = self._mock_children + + for name in names: + child = children.get(name) + if child is None or isinstance(child, _SpecState): + break + else: + # If an autospecced object is attached using attach_mock the + # child would be a function with mock object as attribute from + # which signature has to be derived. + child = _extract_mock(child) + children = child._mock_children + sig = child._spec_signature + + return sig + + + def _call_matcher(self, _call): + """ + Given a call (or simply an (args, kwargs) tuple), return a + comparison key suitable for matching with other calls. + This is a best effort method which relies on the spec's signature, + if available, or falls back on the arguments themselves. + """ + + if isinstance(_call, tuple) and len(_call) > 2: + sig = self._get_call_signature_from_name(_call[0]) + else: + sig = self._spec_signature + + if sig is not None: + if len(_call) == 2: + name = '' + args, kwargs = _call + else: + name, args, kwargs = _call + try: + bound_call = sig.bind(*args, **kwargs) + return call(name, bound_call.args, bound_call.kwargs) + except TypeError as e: + return e.with_traceback(None) + else: + return _call + + def assert_not_called(self): + """assert that the mock was never called. + """ + if self.call_count != 0: + msg = ("Expected '%s' to not have been called. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) + raise AssertionError(msg) + + def assert_called(self): + """assert that the mock was called at least once + """ + if self.call_count == 0: + msg = ("Expected '%s' to have been called." % + (self._mock_name or 'mock')) + raise AssertionError(msg) + + def assert_called_once(self): + """assert that the mock was called only once. + """ + if not self.call_count == 1: + msg = ("Expected '%s' to have been called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) + raise AssertionError(msg) + + def assert_called_with(self, /, *args, **kwargs): + """assert that the last call was made with the specified arguments. + + Raises an AssertionError if the args and keyword args passed in are + different to the last call to the mock.""" + if self.call_args is None: + expected = self._format_mock_call_signature(args, kwargs) + actual = 'not called.' + error_message = ('expected call not found.\nExpected: %s\nActual: %s' + % (expected, actual)) + raise AssertionError(error_message) + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs) + return msg + expected = self._call_matcher(_Call((args, kwargs), two=True)) + actual = self._call_matcher(self.call_args) + if actual != expected: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + + def assert_called_once_with(self, /, *args, **kwargs): + """assert that the mock was called exactly once and that that call was + with the specified arguments.""" + if not self.call_count == 1: + msg = ("Expected '%s' to be called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) + raise AssertionError(msg) + return self.assert_called_with(*args, **kwargs) + + + def assert_has_calls(self, calls, any_order=False): + """assert the mock has been called with the specified calls. + The `mock_calls` list is checked for the calls. + + If `any_order` is False (the default) then the calls must be + sequential. There can be extra calls before or after the + specified calls. + + If `any_order` is True then the calls can be in any order, but + they must all appear in `mock_calls`.""" + expected = [self._call_matcher(c) for c in calls] + cause = next((e for e in expected if isinstance(e, Exception)), None) + all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls) + if not any_order: + if expected not in all_calls: + if cause is None: + problem = 'Calls not found.' + else: + problem = ('Error processing expected calls.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) + raise AssertionError( + f'{problem}\n' + f'Expected: {_CallList(calls)}' + f'{self._calls_repr(prefix="Actual").rstrip(".")}' + ) from cause + return + + all_calls = list(all_calls) + + not_found = [] + for kall in expected: + try: + all_calls.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r does not contain all of %r in its call list, ' + 'found %r instead' % (self._mock_name or 'mock', + tuple(not_found), all_calls) + ) from cause + + + def assert_any_call(self, /, *args, **kwargs): + """assert the mock has been called with the specified arguments. + + The assert passes if the mock has *ever* been called, unlike + `assert_called_with` and `assert_called_once_with` that only pass if + the call is the most recent one.""" + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None + actual = [self._call_matcher(c) for c in self.call_args_list] + if cause or expected not in _AnyComparer(actual): + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s call not found' % expected_string + ) from cause + + + def _get_child_mock(self, /, **kw): + """Create the child mocks for attributes and return value. + By default child mocks will be the same type as the parent. + Subclasses of Mock may want to override this to customize the way + child mocks are made. + + For non-callable mocks the callable variant will be used (rather than + any custom subclass).""" + _new_name = kw.get("_new_name") + if _new_name in self.__dict__['_spec_asyncs']: + return AsyncMock(**kw) + + _type = type(self) + if issubclass(_type, MagicMock) and _new_name in _async_method_magics: + # Any asynchronous magic becomes an AsyncMock + klass = AsyncMock + elif issubclass(_type, AsyncMockMixin): + if (_new_name in _all_sync_magics or + self._mock_methods and _new_name in self._mock_methods): + # Any synchronous method on AsyncMock becomes a MagicMock + klass = MagicMock + else: + klass = AsyncMock + elif not issubclass(_type, CallableMixin): + if issubclass(_type, NonCallableMagicMock): + klass = MagicMock + elif issubclass(_type, NonCallableMock): + klass = Mock + else: + klass = _type.__mro__[1] + + if self._mock_sealed: + attribute = "." + kw["name"] if "name" in kw else "()" + mock_name = self._extract_mock_name() + attribute + raise AttributeError(mock_name) + + return klass(**kw) + + + def _calls_repr(self, prefix="Calls"): + """Renders self.mock_calls as a string. + + Example: "\nCalls: [call(1), call(2)]." + + If self.mock_calls is empty, an empty string is returned. The + output will be truncated if very long. + """ + if not self.mock_calls: + return "" + return f"\n{prefix}: {safe_repr(self.mock_calls)}." + + +_MOCK_SIG = inspect.signature(NonCallableMock.__init__) + + +class _AnyComparer(list): + """A list which checks if it contains a call which may have an + argument of ANY, flipping the components of item and self from + their traditional locations so that ANY is guaranteed to be on + the left.""" + def __contains__(self, item): + for _call in self: + assert len(item) == len(_call) + if all([ + expected == actual + for expected, actual in zip(item, _call) + ]): + return True + return False + + +def _try_iter(obj): + if obj is None: + return obj + if _is_exception(obj): + return obj + if _callable(obj): + return obj + try: + return iter(obj) + except TypeError: + # XXXX backwards compatibility + # but this will blow up on first call - so maybe we should fail early? + return obj + + +class CallableMixin(Base): + + def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, + wraps=None, name=None, spec_set=None, parent=None, + _spec_state=None, _new_name='', _new_parent=None, **kwargs): + self.__dict__['_mock_return_value'] = return_value + _safe_super(CallableMixin, self).__init__( + spec, wraps, name, spec_set, parent, + _spec_state, _new_name, _new_parent, **kwargs + ) + + self.side_effect = side_effect + + + def _mock_check_sig(self, /, *args, **kwargs): + # stub method that can be replaced with one with a specific signature + pass + + + def __call__(self, /, *args, **kwargs): + # can't use self in-case a function / method we are mocking uses self + # in the signature + self._mock_check_sig(*args, **kwargs) + self._increment_mock_call(*args, **kwargs) + return self._mock_call(*args, **kwargs) + + + def _mock_call(self, /, *args, **kwargs): + return self._execute_mock_call(*args, **kwargs) + + def _increment_mock_call(self, /, *args, **kwargs): + self.called = True + self.call_count += 1 + + # handle call_args + # needs to be set here so assertions on call arguments pass before + # execution in the case of awaited calls + _call = _Call((args, kwargs), two=True) + self.call_args = _call + self.call_args_list.append(_call) + + # initial stuff for method_calls: + do_method_calls = self._mock_parent is not None + method_call_name = self._mock_name + + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) + + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + + # handle method_calls: + if do_method_calls: + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) + do_method_calls = _new_parent._mock_parent is not None + if do_method_calls: + method_call_name = _new_parent._mock_name + '.' + method_call_name + + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) + _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: + _new_parent = _new_parent._mock_new_parent + + def _execute_mock_call(self, /, *args, **kwargs): + # separate from _increment_mock_call so that awaited functions are + # executed separately from their call, also AsyncMock overrides this method + + effect = self.side_effect + if effect is not None: + if _is_exception(effect): + raise effect + elif not _callable(effect): + result = next(effect) + if _is_exception(result): + raise result + else: + result = effect(*args, **kwargs) + + if result is not DEFAULT: + return result + + if self._mock_return_value is not DEFAULT: + return self.return_value + + if self._mock_wraps is not None: + return self._mock_wraps(*args, **kwargs) + + return self.return_value + + + +class Mock(CallableMixin, NonCallableMock): + """ + Create a new `Mock` object. `Mock` takes several optional arguments + that specify the behaviour of the Mock object: + + * `spec`: This can be either a list of strings or an existing object (a + class or instance) that acts as the specification for the mock object. If + you pass in an object then a list of strings is formed by calling dir on + the object (excluding unsupported magic attributes and methods). Accessing + any attribute not in this list will raise an `AttributeError`. + + If `spec` is an object (rather than a list of strings) then + `mock.__class__` returns the class of the spec object. This allows mocks + to pass `isinstance` tests. + + * `spec_set`: A stricter variant of `spec`. If used, attempting to *set* + or get an attribute on the mock that isn't on the object passed as + `spec_set` will raise an `AttributeError`. + + * `side_effect`: A function to be called whenever the Mock is called. See + the `side_effect` attribute. Useful for raising exceptions or + dynamically changing return values. The function is called with the same + arguments as the mock, and unless it returns `DEFAULT`, the return + value of this function is used as the return value. + + If `side_effect` is an iterable then each call to the mock will return + the next value from the iterable. If any of the members of the iterable + are exceptions they will be raised instead of returned. + + * `return_value`: The value returned when the mock is called. By default + this is a new Mock (created on first access). See the + `return_value` attribute. + + * `wraps`: Item for the mock object to wrap. If `wraps` is not None then + calling the Mock will pass the call through to the wrapped object + (returning the real result). Attribute access on the mock will return a + Mock object that wraps the corresponding attribute of the wrapped object + (so attempting to access an attribute that doesn't exist will raise an + `AttributeError`). + + If the mock has an explicit `return_value` set then calls are not passed + to the wrapped object and the `return_value` is returned instead. + + * `name`: If the mock has a name then it will be used in the repr of the + mock. This can be useful for debugging. The name is propagated to child + mocks. + + Mocks can also be called with arbitrary keyword arguments. These will be + used to set attributes on the mock after it is created. + """ + + +def _dot_lookup(thing, comp, import_path): + try: + return getattr(thing, comp) + except AttributeError: + __import__(import_path) + return getattr(thing, comp) + + +def _importer(target): + components = target.split('.') + import_path = components.pop(0) + thing = __import__(import_path) + + for comp in components: + import_path += ".%s" % comp + thing = _dot_lookup(thing, comp, import_path) + return thing + + +class _patch(object): + + attribute_name = None + _active_patches = [] + + def __init__( + self, getter, attribute, new, spec, create, + spec_set, autospec, new_callable, kwargs + ): + if new_callable is not None: + if new is not DEFAULT: + raise ValueError( + "Cannot use 'new' and 'new_callable' together" + ) + if autospec is not None: + raise ValueError( + "Cannot use 'autospec' and 'new_callable' together" + ) + + self.getter = getter + self.attribute = attribute + self.new = new + self.new_callable = new_callable + self.spec = spec + self.create = create + self.has_local = False + self.spec_set = spec_set + self.autospec = autospec + self.kwargs = kwargs + self.additional_patchers = [] + + + def copy(self): + patcher = _patch( + self.getter, self.attribute, self.new, self.spec, + self.create, self.spec_set, + self.autospec, self.new_callable, self.kwargs + ) + patcher.attribute_name = self.attribute_name + patcher.additional_patchers = [ + p.copy() for p in self.additional_patchers + ] + return patcher + + + def __call__(self, func): + if isinstance(func, type): + return self.decorate_class(func) + if inspect.iscoroutinefunction(func): + return self.decorate_async_callable(func) + return self.decorate_callable(func) + + + def decorate_class(self, klass): + for attr in dir(klass): + if not attr.startswith(patch.TEST_PREFIX): + continue + + attr_value = getattr(klass, attr) + if not hasattr(attr_value, "__call__"): + continue + + patcher = self.copy() + setattr(klass, attr, patcher(attr_value)) + return klass + + + @contextlib.contextmanager + def decoration_helper(self, patched, args, keywargs): + extra_args = [] + with contextlib.ExitStack() as exit_stack: + for patching in patched.patchings: + arg = exit_stack.enter_context(patching) + if patching.attribute_name is not None: + keywargs.update(arg) + elif patching.new is DEFAULT: + extra_args.append(arg) + + args += tuple(extra_args) + yield (args, keywargs) + + + def decorate_callable(self, func): + # NB. Keep the method in sync with decorate_async_callable() + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + @wraps(func) + def patched(*args, **keywargs): + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return func(*newargs, **newkeywargs) + + patched.patchings = [self] + return patched + + + def decorate_async_callable(self, func): + # NB. Keep the method in sync with decorate_callable() + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + @wraps(func) + async def patched(*args, **keywargs): + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return await func(*newargs, **newkeywargs) + + patched.patchings = [self] + return patched + + + def get_original(self): + target = self.getter() + name = self.attribute + + original = DEFAULT + local = False + + try: + original = target.__dict__[name] + except (AttributeError, KeyError): + original = getattr(target, name, DEFAULT) + else: + local = True + + if name in _builtins and isinstance(target, ModuleType): + self.create = True + + if not self.create and original is DEFAULT: + raise AttributeError( + "%s does not have the attribute %r" % (target, name) + ) + return original, local + + + def __enter__(self): + """Perform the patch.""" + new, spec, spec_set = self.new, self.spec, self.spec_set + autospec, kwargs = self.autospec, self.kwargs + new_callable = self.new_callable + self.target = self.getter() + + # normalise False to None + if spec is False: + spec = None + if spec_set is False: + spec_set = None + if autospec is False: + autospec = None + + if spec is not None and autospec is not None: + raise TypeError("Can't specify spec and autospec") + if ((spec is not None or autospec is not None) and + spec_set not in (True, None)): + raise TypeError("Can't provide explicit spec_set *and* spec or autospec") + + original, local = self.get_original() + + if new is DEFAULT and autospec is None: + inherit = False + if spec is True: + # set spec to the object we are replacing + spec = original + if spec_set is True: + spec_set = original + spec = None + elif spec is not None: + if spec_set is True: + spec_set = spec + spec = None + elif spec_set is True: + spec_set = original + + if spec is not None or spec_set is not None: + if original is DEFAULT: + raise TypeError("Can't use 'spec' with create=True") + if isinstance(original, type): + # If we're patching out a class and there is a spec + inherit = True + if spec is None and _is_async_obj(original): + Klass = AsyncMock + else: + Klass = MagicMock + _kwargs = {} + if new_callable is not None: + Klass = new_callable + elif spec is not None or spec_set is not None: + this_spec = spec + if spec_set is not None: + this_spec = spec_set + if _is_list(this_spec): + not_callable = '__call__' not in this_spec + else: + not_callable = not callable(this_spec) + if _is_async_obj(this_spec): + Klass = AsyncMock + elif not_callable: + Klass = NonCallableMagicMock + + if spec is not None: + _kwargs['spec'] = spec + if spec_set is not None: + _kwargs['spec_set'] = spec_set + + # add a name to mocks + if (isinstance(Klass, type) and + issubclass(Klass, NonCallableMock) and self.attribute): + _kwargs['name'] = self.attribute + + _kwargs.update(kwargs) + new = Klass(**_kwargs) + + if inherit and _is_instance_mock(new): + # we can only tell if the instance should be callable if the + # spec is not a list + this_spec = spec + if spec_set is not None: + this_spec = spec_set + if (not _is_list(this_spec) and not + _instance_callable(this_spec)): + Klass = NonCallableMagicMock + + _kwargs.pop('name') + new.return_value = Klass(_new_parent=new, _new_name='()', + **_kwargs) + elif autospec is not None: + # spec is ignored, new *must* be default, spec_set is treated + # as a boolean. Should we check spec is not None and that spec_set + # is a bool? + if new is not DEFAULT: + raise TypeError( + "autospec creates the mock for you. Can't specify " + "autospec and new." + ) + if original is DEFAULT: + raise TypeError("Can't use 'autospec' with create=True") + spec_set = bool(spec_set) + if autospec is True: + autospec = original + + new = create_autospec(autospec, spec_set=spec_set, + _name=self.attribute, **kwargs) + elif kwargs: + # can't set keyword args when we aren't creating the mock + # XXXX If new is a Mock we could call new.configure_mock(**kwargs) + raise TypeError("Can't pass kwargs to a mock we aren't creating") + + new_attr = new + + self.temp_original = original + self.is_local = local + self._exit_stack = contextlib.ExitStack() + try: + setattr(self.target, self.attribute, new_attr) + if self.attribute_name is not None: + extra_args = {} + if self.new is DEFAULT: + extra_args[self.attribute_name] = new + for patching in self.additional_patchers: + arg = self._exit_stack.enter_context(patching) + if patching.new is DEFAULT: + extra_args.update(arg) + return extra_args + + return new + except: + if not self.__exit__(*sys.exc_info()): + raise + + def __exit__(self, *exc_info): + """Undo the patch.""" + if self.is_local and self.temp_original is not DEFAULT: + setattr(self.target, self.attribute, self.temp_original) + else: + delattr(self.target, self.attribute) + if not self.create and (not hasattr(self.target, self.attribute) or + self.attribute in ('__doc__', '__module__', + '__defaults__', '__annotations__', + '__kwdefaults__')): + # needed for proxy objects like django settings + setattr(self.target, self.attribute, self.temp_original) + + del self.temp_original + del self.is_local + del self.target + exit_stack = self._exit_stack + del self._exit_stack + return exit_stack.__exit__(*exc_info) + + + def start(self): + """Activate a patch, returning any created mock.""" + result = self.__enter__() + self._active_patches.append(self) + return result + + + def stop(self): + """Stop an active patch.""" + try: + self._active_patches.remove(self) + except ValueError: + # If the patch hasn't been started this will fail + return None + + return self.__exit__(None, None, None) + + + +def _get_target(target): + try: + target, attribute = target.rsplit('.', 1) + except (TypeError, ValueError): + raise TypeError("Need a valid target to patch. You supplied: %r" % + (target,)) + getter = lambda: _importer(target) + return getter, attribute + + +def _patch_object( + target, attribute, new=DEFAULT, spec=None, + create=False, spec_set=None, autospec=None, + new_callable=None, **kwargs + ): + """ + patch the named member (`attribute`) on an object (`target`) with a mock + object. + + `patch.object` can be used as a decorator, class decorator or a context + manager. Arguments `new`, `spec`, `create`, `spec_set`, + `autospec` and `new_callable` have the same meaning as for `patch`. Like + `patch`, `patch.object` takes arbitrary keyword arguments for configuring + the mock object it creates. + + When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + """ + if type(target) is str: + raise TypeError( + f"{target!r} must be the actual object to be patched, not a str" + ) + getter = lambda: target + return _patch( + getter, attribute, new, spec, create, + spec_set, autospec, new_callable, kwargs + ) + + +def _patch_multiple(target, spec=None, create=False, spec_set=None, + autospec=None, new_callable=None, **kwargs): + """Perform multiple patches in a single call. It takes the object to be + patched (either as an object or a string to fetch the object by importing) + and keyword arguments for the patches:: + + with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): + ... + + Use `DEFAULT` as the value if you want `patch.multiple` to create + mocks for you. In this case the created mocks are passed into a decorated + function by keyword, and a dictionary is returned when `patch.multiple` is + used as a context manager. + + `patch.multiple` can be used as a decorator, class decorator or a context + manager. The arguments `spec`, `spec_set`, `create`, + `autospec` and `new_callable` have the same meaning as for `patch`. These + arguments will be applied to *all* patches done by `patch.multiple`. + + When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + """ + if type(target) is str: + getter = lambda: _importer(target) + else: + getter = lambda: target + + if not kwargs: + raise ValueError( + 'Must supply at least one keyword argument with patch.multiple' + ) + # need to wrap in a list for python 3, where items is a view + items = list(kwargs.items()) + attribute, new = items[0] + patcher = _patch( + getter, attribute, new, spec, create, spec_set, + autospec, new_callable, {} + ) + patcher.attribute_name = attribute + for attribute, new in items[1:]: + this_patcher = _patch( + getter, attribute, new, spec, create, spec_set, + autospec, new_callable, {} + ) + this_patcher.attribute_name = attribute + patcher.additional_patchers.append(this_patcher) + return patcher + + +def patch( + target, new=DEFAULT, spec=None, create=False, + spec_set=None, autospec=None, new_callable=None, **kwargs + ): + """ + `patch` acts as a function decorator, class decorator or a context + manager. Inside the body of the function or with statement, the `target` + is patched with a `new` object. When the function/with statement exits + the patch is undone. + + If `new` is omitted, then the target is replaced with an + `AsyncMock if the patched object is an async function or a + `MagicMock` otherwise. If `patch` is used as a decorator and `new` is + omitted, the created mock is passed in as an extra argument to the + decorated function. If `patch` is used as a context manager the created + mock is returned by the context manager. + + `target` should be a string in the form `'package.module.ClassName'`. The + `target` is imported and the specified object replaced with the `new` + object, so the `target` must be importable from the environment you are + calling `patch` from. The target is imported when the decorated function + is executed, not at decoration time. + + The `spec` and `spec_set` keyword arguments are passed to the `MagicMock` + if patch is creating one for you. + + In addition you can pass `spec=True` or `spec_set=True`, which causes + patch to pass in the object being mocked as the spec/spec_set object. + + `new_callable` allows you to specify a different class, or callable object, + that will be called to create the `new` object. By default `AsyncMock` is + used for async functions and `MagicMock` for the rest. + + A more powerful form of `spec` is `autospec`. If you set `autospec=True` + then the mock will be created with a spec from the object being replaced. + All attributes of the mock will also have the spec of the corresponding + attribute of the object being replaced. Methods and functions being + mocked will have their arguments checked and will raise a `TypeError` if + they are called with the wrong signature. For mocks replacing a class, + their return value (the 'instance') will have the same spec as the class. + + Instead of `autospec=True` you can pass `autospec=some_object` to use an + arbitrary object as the spec instead of the one being replaced. + + By default `patch` will fail to replace attributes that don't exist. If + you pass in `create=True`, and the attribute doesn't exist, patch will + create the attribute for you when the patched function is called, and + delete it again afterwards. This is useful for writing tests against + attributes that your production code creates at runtime. It is off by + default because it can be dangerous. With it switched on you can write + passing tests against APIs that don't actually exist! + + Patch can be used as a `TestCase` class decorator. It works by + decorating each test method in the class. This reduces the boilerplate + code when your test methods share a common patchings set. `patch` finds + tests by looking for method names that start with `patch.TEST_PREFIX`. + By default this is `test`, which matches the way `unittest` finds tests. + You can specify an alternative prefix by setting `patch.TEST_PREFIX`. + + Patch can be used as a context manager, with the with statement. Here the + patching applies to the indented block after the with statement. If you + use "as" then the patched object will be bound to the name after the + "as"; very useful if `patch` is creating a mock object for you. + + `patch` takes arbitrary keyword arguments. These will be passed to + `AsyncMock` if the patched object is asynchronous, to `MagicMock` + otherwise or to `new_callable` if specified. + + `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are + available for alternate use-cases. + """ + getter, attribute = _get_target(target) + return _patch( + getter, attribute, new, spec, create, + spec_set, autospec, new_callable, kwargs + ) + + +class _patch_dict(object): + """ + Patch a dictionary, or dictionary like object, and restore the dictionary + to its original state after the test. + + `in_dict` can be a dictionary or a mapping like container. If it is a + mapping then it must at least support getting, setting and deleting items + plus iterating over keys. + + `in_dict` can also be a string specifying the name of the dictionary, which + will then be fetched by importing it. + + `values` can be a dictionary of values to set in the dictionary. `values` + can also be an iterable of `(key, value)` pairs. + + If `clear` is True then the dictionary will be cleared before the new + values are set. + + `patch.dict` can also be called with arbitrary keyword arguments to set + values in the dictionary:: + + with patch.dict('sys.modules', mymodule=Mock(), other_module=Mock()): + ... + + `patch.dict` can be used as a context manager, decorator or class + decorator. When used as a class decorator `patch.dict` honours + `patch.TEST_PREFIX` for choosing which methods to wrap. + """ + + def __init__(self, in_dict, values=(), clear=False, **kwargs): + self.in_dict = in_dict + # support any argument supported by dict(...) constructor + self.values = dict(values) + self.values.update(kwargs) + self.clear = clear + self._original = None + + + def __call__(self, f): + if isinstance(f, type): + return self.decorate_class(f) + @wraps(f) + def _inner(*args, **kw): + self._patch_dict() + try: + return f(*args, **kw) + finally: + self._unpatch_dict() + + return _inner + + + def decorate_class(self, klass): + for attr in dir(klass): + attr_value = getattr(klass, attr) + if (attr.startswith(patch.TEST_PREFIX) and + hasattr(attr_value, "__call__")): + decorator = _patch_dict(self.in_dict, self.values, self.clear) + decorated = decorator(attr_value) + setattr(klass, attr, decorated) + return klass + + + def __enter__(self): + """Patch the dict.""" + self._patch_dict() + return self.in_dict + + + def _patch_dict(self): + values = self.values + if isinstance(self.in_dict, str): + self.in_dict = _importer(self.in_dict) + in_dict = self.in_dict + clear = self.clear + + try: + original = in_dict.copy() + except AttributeError: + # dict like object with no copy method + # must support iteration over keys + original = {} + for key in in_dict: + original[key] = in_dict[key] + self._original = original + + if clear: + _clear_dict(in_dict) + + try: + in_dict.update(values) + except AttributeError: + # dict like object with no update method + for key in values: + in_dict[key] = values[key] + + + def _unpatch_dict(self): + in_dict = self.in_dict + original = self._original + + _clear_dict(in_dict) + + try: + in_dict.update(original) + except AttributeError: + for key in original: + in_dict[key] = original[key] + + + def __exit__(self, *args): + """Unpatch the dict.""" + if self._original is not None: + self._unpatch_dict() + return False + + + def start(self): + """Activate a patch, returning any created mock.""" + result = self.__enter__() + _patch._active_patches.append(self) + return result + + + def stop(self): + """Stop an active patch.""" + try: + _patch._active_patches.remove(self) + except ValueError: + # If the patch hasn't been started this will fail + return None + + return self.__exit__(None, None, None) + + +def _clear_dict(in_dict): + try: + in_dict.clear() + except AttributeError: + keys = list(in_dict) + for key in keys: + del in_dict[key] + + +def _patch_stopall(): + """Stop all active patches. LIFO to unroll nested patches.""" + for patch in reversed(_patch._active_patches): + patch.stop() + + +patch.object = _patch_object +patch.dict = _patch_dict +patch.multiple = _patch_multiple +patch.stopall = _patch_stopall +patch.TEST_PREFIX = 'test' + +magic_methods = ( + "lt le gt ge eq ne " + "getitem setitem delitem " + "len contains iter " + "hash str sizeof " + "enter exit " + # we added divmod and rdivmod here instead of numerics + # because there is no idivmod + "divmod rdivmod neg pos abs invert " + "complex int float index " + "round trunc floor ceil " + "bool next " + "fspath " + "aiter " +) + +numerics = ( + "add sub mul matmul div floordiv mod lshift rshift and xor or pow truediv" +) +inplace = ' '.join('i%s' % n for n in numerics.split()) +right = ' '.join('r%s' % n for n in numerics.split()) + +# not including __prepare__, __instancecheck__, __subclasscheck__ +# (as they are metaclass methods) +# __del__ is not supported at all as it causes problems if it exists + +_non_defaults = { + '__get__', '__set__', '__delete__', '__reversed__', '__missing__', + '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', + '__getstate__', '__setstate__', '__getformat__', '__setformat__', + '__repr__', '__dir__', '__subclasses__', '__format__', + '__getnewargs_ex__', +} + + +def _get_method(name, func): + "Turns a callable object (like a mock) into a real function" + def method(self, /, *args, **kw): + return func(self, *args, **kw) + method.__name__ = name + return method + + +_magics = { + '__%s__' % method for method in + ' '.join([magic_methods, numerics, inplace, right]).split() +} + +# Magic methods used for async `with` statements +_async_method_magics = {"__aenter__", "__aexit__", "__anext__"} +# Magic methods that are only used with async calls but are synchronous functions themselves +_sync_async_magics = {"__aiter__"} +_async_magics = _async_method_magics | _sync_async_magics + +_all_sync_magics = _magics | _non_defaults +_all_magics = _all_sync_magics | _async_magics + +_unsupported_magics = { + '__getattr__', '__setattr__', + '__init__', '__new__', '__prepare__', + '__instancecheck__', '__subclasscheck__', + '__del__' +} + +_calculate_return_value = { + '__hash__': lambda self: object.__hash__(self), + '__str__': lambda self: object.__str__(self), + '__sizeof__': lambda self: object.__sizeof__(self), + '__fspath__': lambda self: f"{type(self).__name__}/{self._extract_mock_name()}/{id(self)}", +} + +_return_values = { + '__lt__': NotImplemented, + '__gt__': NotImplemented, + '__le__': NotImplemented, + '__ge__': NotImplemented, + '__int__': 1, + '__contains__': False, + '__len__': 0, + '__exit__': False, + '__complex__': 1j, + '__float__': 1.0, + '__bool__': True, + '__index__': 1, + '__aexit__': False, +} + + +def _get_eq(self): + def __eq__(other): + ret_val = self.__eq__._mock_return_value + if ret_val is not DEFAULT: + return ret_val + if self is other: + return True + return NotImplemented + return __eq__ + +def _get_ne(self): + def __ne__(other): + if self.__ne__._mock_return_value is not DEFAULT: + return DEFAULT + if self is other: + return False + return NotImplemented + return __ne__ + +def _get_iter(self): + def __iter__(): + ret_val = self.__iter__._mock_return_value + if ret_val is DEFAULT: + return iter([]) + # if ret_val was already an iterator, then calling iter on it should + # return the iterator unchanged + return iter(ret_val) + return __iter__ + +def _get_async_iter(self): + def __aiter__(): + ret_val = self.__aiter__._mock_return_value + if ret_val is DEFAULT: + return _AsyncIterator(iter([])) + return _AsyncIterator(iter(ret_val)) + return __aiter__ + +_side_effect_methods = { + '__eq__': _get_eq, + '__ne__': _get_ne, + '__iter__': _get_iter, + '__aiter__': _get_async_iter +} + + + +def _set_return_value(mock, method, name): + fixed = _return_values.get(name, DEFAULT) + if fixed is not DEFAULT: + method.return_value = fixed + return + + return_calculator = _calculate_return_value.get(name) + if return_calculator is not None: + return_value = return_calculator(mock) + method.return_value = return_value + return + + side_effector = _side_effect_methods.get(name) + if side_effector is not None: + method.side_effect = side_effector(mock) + + + +class MagicMixin(Base): + def __init__(self, /, *args, **kw): + self._mock_set_magics() # make magic work for kwargs in init + _safe_super(MagicMixin, self).__init__(*args, **kw) + self._mock_set_magics() # fix magic broken by upper level init + + + def _mock_set_magics(self): + orig_magics = _magics | _async_method_magics + these_magics = orig_magics + + if getattr(self, "_mock_methods", None) is not None: + these_magics = orig_magics.intersection(self._mock_methods) + + remove_magics = set() + remove_magics = orig_magics - these_magics + + for entry in remove_magics: + if entry in type(self).__dict__: + # remove unneeded magic methods + delattr(self, entry) + + # don't overwrite existing attributes if called a second time + these_magics = these_magics - set(type(self).__dict__) + + _type = type(self) + for entry in these_magics: + setattr(_type, entry, MagicProxy(entry, self)) + + + +class NonCallableMagicMock(MagicMixin, NonCallableMock): + """A version of `MagicMock` that isn't callable.""" + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + self._mock_set_magics() + + +class AsyncMagicMixin(MagicMixin): + def __init__(self, /, *args, **kw): + self._mock_set_magics() # make magic work for kwargs in init + _safe_super(AsyncMagicMixin, self).__init__(*args, **kw) + self._mock_set_magics() # fix magic broken by upper level init + +class MagicMock(MagicMixin, Mock): + """ + MagicMock is a subclass of Mock with default implementations + of most of the magic methods. You can use MagicMock without having to + configure the magic methods yourself. + + If you use the `spec` or `spec_set` arguments then *only* magic + methods that exist in the spec will be created. + + Attributes and the return value of a `MagicMock` will also be `MagicMocks`. + """ + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + self._mock_set_magics() + + + +class MagicProxy(Base): + def __init__(self, name, parent): + self.name = name + self.parent = parent + + def create_mock(self): + entry = self.name + parent = self.parent + m = parent._get_child_mock(name=entry, _new_name=entry, + _new_parent=parent) + setattr(parent, entry, m) + _set_return_value(parent, m, entry) + return m + + def __get__(self, obj, _type=None): + return self.create_mock() + + +class AsyncMockMixin(Base): + await_count = _delegating_property('await_count') + await_args = _delegating_property('await_args') + await_args_list = _delegating_property('await_args_list') + + def __init__(self, /, *args, **kwargs): + super().__init__(*args, **kwargs) + # iscoroutinefunction() checks _is_coroutine property to say if an + # object is a coroutine. Without this check it looks to see if it is a + # function/method, which in this case it is not (since it is an + # AsyncMock). + # It is set through __dict__ because when spec_set is True, this + # attribute is likely undefined. + self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine + self.__dict__['_mock_await_count'] = 0 + self.__dict__['_mock_await_args'] = None + self.__dict__['_mock_await_args_list'] = _CallList() + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_COROUTINE + self.__dict__['__code__'] = code_mock + + async def _execute_mock_call(self, /, *args, **kwargs): + # This is nearly just like super(), except for special handling + # of coroutines + + _call = _Call((args, kwargs), two=True) + self.await_count += 1 + self.await_args = _call + self.await_args_list.append(_call) + + effect = self.side_effect + if effect is not None: + if _is_exception(effect): + raise effect + elif not _callable(effect): + try: + result = next(effect) + except StopIteration: + # It is impossible to propogate a StopIteration + # through coroutines because of PEP 479 + raise StopAsyncIteration + if _is_exception(result): + raise result + elif iscoroutinefunction(effect): + result = await effect(*args, **kwargs) + else: + result = effect(*args, **kwargs) + + if result is not DEFAULT: + return result + + if self._mock_return_value is not DEFAULT: + return self.return_value + + if self._mock_wraps is not None: + if iscoroutinefunction(self._mock_wraps): + return await self._mock_wraps(*args, **kwargs) + return self._mock_wraps(*args, **kwargs) + + return self.return_value + + def assert_awaited(self): + """ + Assert that the mock was awaited at least once. + """ + if self.await_count == 0: + msg = f"Expected {self._mock_name or 'mock'} to have been awaited." + raise AssertionError(msg) + + def assert_awaited_once(self): + """ + Assert that the mock was awaited exactly once. + """ + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def assert_awaited_with(self, /, *args, **kwargs): + """ + Assert that the last await was with the specified arguments. + """ + if self.await_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'Expected await: {expected}\nNot awaited') + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs, action='await') + return msg + + expected = self._call_matcher(_Call((args, kwargs), two=True)) + actual = self._call_matcher(self.await_args) + if actual != expected: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + def assert_awaited_once_with(self, /, *args, **kwargs): + """ + Assert that the mock was awaited exactly once and with the specified + arguments. + """ + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + return self.assert_awaited_with(*args, **kwargs) + + def assert_any_await(self, /, *args, **kwargs): + """ + Assert the mock has ever been awaited with the specified arguments. + """ + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None + actual = [self._call_matcher(c) for c in self.await_args_list] + if cause or expected not in _AnyComparer(actual): + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s await not found' % expected_string + ) from cause + + def assert_has_awaits(self, calls, any_order=False): + """ + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + """ + expected = [self._call_matcher(c) for c in calls] + cause = next((e for e in expected if isinstance(e, Exception)), None) + all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) + if not any_order: + if expected not in all_awaits: + if cause is None: + problem = 'Awaits not found.' + else: + problem = ('Error processing expected awaits.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) + raise AssertionError( + f'{problem}\n' + f'Expected: {_CallList(calls)}\n' + f'Actual: {self.await_args_list}' + ) from cause + return + + all_awaits = list(all_awaits) + + not_found = [] + for kall in expected: + try: + all_awaits.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in await list' % (tuple(not_found),) + ) from cause + + def assert_not_awaited(self): + """ + Assert that the mock was never awaited. + """ + if self.await_count != 0: + msg = (f"Expected {self._mock_name or 'mock'} to not have been awaited." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def reset_mock(self, /, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.await_count = 0 + self.await_args = None + self.await_args_list = _CallList() + + +class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): + """ + Enhance :class:`Mock` with features allowing to mock + an async function. + + The :class:`AsyncMock` object will behave so the object is + recognized as an async function, and the result of a call is an awaitable: + + >>> mock = AsyncMock() + >>> iscoroutinefunction(mock) + True + >>> inspect.isawaitable(mock()) + True + + + The result of ``mock()`` is an async function which will have the outcome + of ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the async function will return the + result of that function, + - if ``side_effect`` is an exception, the async function will raise the + exception, + - if ``side_effect`` is an iterable, the async function will return the + next value of the iterable, however, if the sequence of result is + exhausted, ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the async function will return the + value defined by ``return_value``, hence, by default, the async function + returns a new :class:`AsyncMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is an async function, + the mock async function obtained when the mock object is called will be this + async function itself (and not an async function returning an async + function). + + The test author can also specify a wrapped object with ``wraps``. In this + case, the :class:`Mock` object behavior is the same as with an + :class:`.Mock` object: the wrapped object may have methods + defined as async function functions. + + Based on Martin Richard's asynctest project. + """ + + +class _ANY(object): + "A helper object that compares equal to everything." + + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + + def __repr__(self): + return '' + +ANY = _ANY() + + + +def _format_call_signature(name, args, kwargs): + message = '%s(%%s)' % name + formatted_args = '' + args_string = ', '.join([repr(arg) for arg in args]) + kwargs_string = ', '.join([ + '%s=%r' % (key, value) for key, value in kwargs.items() + ]) + if args_string: + formatted_args = args_string + if kwargs_string: + if formatted_args: + formatted_args += ', ' + formatted_args += kwargs_string + + return message % formatted_args + + + +class _Call(tuple): + """ + A tuple for holding the results of a call to a mock, either in the form + `(args, kwargs)` or `(name, args, kwargs)`. + + If args or kwargs are empty then a call tuple will compare equal to + a tuple without those values. This makes comparisons less verbose:: + + _Call(('name', (), {})) == ('name',) + _Call(('name', (1,), {})) == ('name', (1,)) + _Call(((), {'a': 'b'})) == ({'a': 'b'},) + + The `_Call` object provides a useful shortcut for comparing with call:: + + _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) + _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) + + If the _Call has no name then it will match any name. + """ + def __new__(cls, value=(), name='', parent=None, two=False, + from_kall=True): + args = () + kwargs = {} + _len = len(value) + if _len == 3: + name, args, kwargs = value + elif _len == 2: + first, second = value + if isinstance(first, str): + name = first + if isinstance(second, tuple): + args = second + else: + kwargs = second + else: + args, kwargs = first, second + elif _len == 1: + value, = value + if isinstance(value, str): + name = value + elif isinstance(value, tuple): + args = value + else: + kwargs = value + + if two: + return tuple.__new__(cls, (args, kwargs)) + + return tuple.__new__(cls, (name, args, kwargs)) + + + def __init__(self, value=(), name=None, parent=None, two=False, + from_kall=True): + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall + + + def __eq__(self, other): + try: + len_other = len(other) + except TypeError: + return NotImplemented + + self_name = '' + if len(self) == 2: + self_args, self_kwargs = self + else: + self_name, self_args, self_kwargs = self + + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): + return False + + other_name = '' + if len_other == 0: + other_args, other_kwargs = (), {} + elif len_other == 3: + other_name, other_args, other_kwargs = other + elif len_other == 1: + value, = other + if isinstance(value, tuple): + other_args = value + other_kwargs = {} + elif isinstance(value, str): + other_name = value + other_args, other_kwargs = (), {} + else: + other_args = () + other_kwargs = value + elif len_other == 2: + # could be (name, args) or (name, kwargs) or (args, kwargs) + first, second = other + if isinstance(first, str): + other_name = first + if isinstance(second, tuple): + other_args, other_kwargs = second, {} + else: + other_args, other_kwargs = (), second + else: + other_args, other_kwargs = first, second + else: + return False + + if self_name and other_name != self_name: + return False + + # this order is important for ANY to work! + return (other_args, other_kwargs) == (self_args, self_kwargs) + + + __ne__ = object.__ne__ + + + def __call__(self, /, *args, **kwargs): + if self._mock_name is None: + return _Call(('', args, kwargs), name='()') + + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) + + + def __getattr__(self, attr): + if self._mock_name is None: + return _Call(name=attr, from_kall=False) + name = '%s.%s' % (self._mock_name, attr) + return _Call(name=name, parent=self, from_kall=False) + + + def __getattribute__(self, attr): + if attr in tuple.__dict__: + raise AttributeError + return tuple.__getattribute__(self, attr) + + + def _get_call_arguments(self): + if len(self) == 2: + args, kwargs = self + else: + name, args, kwargs = self + + return args, kwargs + + @property + def args(self): + return self._get_call_arguments()[0] + + @property + def kwargs(self): + return self._get_call_arguments()[1] + + def __repr__(self): + if not self._mock_from_kall: + name = self._mock_name or 'call' + if name.startswith('()'): + name = 'call%s' % name + return name + + if len(self) == 2: + name = 'call' + args, kwargs = self + else: + name, args, kwargs = self + if not name: + name = 'call' + elif not name.startswith('()'): + name = 'call.%s' % name + else: + name = 'call%s' % name + return _format_call_signature(name, args, kwargs) + + + def call_list(self): + """For a call object that represents multiple calls, `call_list` + returns a list of all the intermediate calls as well as the + final call.""" + vals = [] + thing = self + while thing is not None: + if thing._mock_from_kall: + vals.append(thing) + thing = thing._mock_parent + return _CallList(reversed(vals)) + + +call = _Call(from_kall=False) + + +def create_autospec(spec, spec_set=False, instance=False, _parent=None, + _name=None, **kwargs): + """Create a mock object using another object as a spec. Attributes on the + mock will use the corresponding attribute on the `spec` object as their + spec. + + Functions or methods being mocked will have their arguments checked + to check that they are called with the correct signature. + + If `spec_set` is True then attempting to set attributes that don't exist + on the spec object will raise an `AttributeError`. + + If a class is used as a spec then the return value of the mock (the + instance of the class) will have the same spec. You can use a class as the + spec for an instance object by passing `instance=True`. The returned mock + will only be callable if instances of the mock are callable. + + `create_autospec` also takes arbitrary keyword arguments that are passed to + the constructor of the created mock.""" + if _is_list(spec): + # can't pass a list instance to the mock constructor as it will be + # interpreted as a list of strings + spec = type(spec) + + is_type = isinstance(spec, type) + is_async_func = _is_async_func(spec) + _kwargs = {'spec': spec} + if spec_set: + _kwargs = {'spec_set': spec} + elif spec is None: + # None we mock with a normal mock without a spec + _kwargs = {} + if _kwargs and instance: + _kwargs['_spec_as_instance'] = True + + _kwargs.update(kwargs) + + Klass = MagicMock + if inspect.isdatadescriptor(spec): + # descriptors don't have a spec + # because we don't know what type they return + _kwargs = {} + elif is_async_func: + if instance: + raise RuntimeError("Instance can not be True when create_autospec " + "is mocking an async function") + Klass = AsyncMock + elif not _callable(spec): + Klass = NonCallableMagicMock + elif is_type and instance and not _instance_callable(spec): + Klass = NonCallableMagicMock + + _name = _kwargs.pop('name', _name) + + _new_name = _name + if _parent is None: + # for a top level object no _new_name should be set + _new_name = '' + + mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, + name=_name, **_kwargs) + + if isinstance(spec, FunctionTypes): + # should only happen at the top level because we don't + # recurse for functions + mock = _set_signature(mock, spec) + if is_async_func: + _setup_async_mock(mock) + else: + _check_signature(spec, mock, is_type, instance) + + if _parent is not None and not instance: + _parent._mock_children[_name] = mock + + if is_type and not instance and 'return_value' not in kwargs: + mock.return_value = create_autospec(spec, spec_set, instance=True, + _name='()', _parent=mock) + + for entry in dir(spec): + if _is_magic(entry): + # MagicMock already does the useful magic methods for us + continue + + # XXXX do we need a better way of getting attributes without + # triggering code execution (?) Probably not - we need the actual + # object to mock it so we would rather trigger a property than mock + # the property descriptor. Likewise we want to mock out dynamically + # provided attributes. + # XXXX what about attributes that raise exceptions other than + # AttributeError on being fetched? + # we could be resilient against it, or catch and propagate the + # exception when the attribute is fetched from the mock + try: + original = getattr(spec, entry) + except AttributeError: + continue + + kwargs = {'spec': original} + if spec_set: + kwargs = {'spec_set': original} + + if not isinstance(original, FunctionTypes): + new = _SpecState(original, spec_set, mock, entry, instance) + mock._mock_children[entry] = new + else: + parent = mock + if isinstance(spec, FunctionTypes): + parent = mock.mock + + skipfirst = _must_skip(spec, entry, is_type) + kwargs['_eat_self'] = skipfirst + if iscoroutinefunction(original): + child_klass = AsyncMock + else: + child_klass = MagicMock + new = child_klass(parent=parent, name=entry, _new_name=entry, + _new_parent=parent, + **kwargs) + mock._mock_children[entry] = new + _check_signature(original, new, skipfirst=skipfirst) + + # so functions created with _set_signature become instance attributes, + # *plus* their underlying mock exists in _mock_children of the parent + # mock. Adding to _mock_children may be unnecessary where we are also + # setting as an instance attribute? + if isinstance(new, FunctionTypes): + setattr(mock, entry, new) + + return mock + + +def _must_skip(spec, entry, is_type): + """ + Return whether we should skip the first argument on spec's `entry` + attribute. + """ + if not isinstance(spec, type): + if entry in getattr(spec, '__dict__', {}): + # instance attribute - shouldn't skip + return False + spec = spec.__class__ + + for klass in spec.__mro__: + result = klass.__dict__.get(entry, DEFAULT) + if result is DEFAULT: + continue + if isinstance(result, (staticmethod, classmethod)): + return False + elif isinstance(result, FunctionTypes): + # Normal method => skip if looked up on type + # (if looked up on instance, self is already skipped) + return is_type + else: + return False + + # function is a dynamically provided attribute + return is_type + + +class _SpecState(object): + + def __init__(self, spec, spec_set=False, parent=None, + name=None, ids=None, instance=False): + self.spec = spec + self.ids = ids + self.spec_set = spec_set + self.parent = parent + self.instance = instance + self.name = name + + +FunctionTypes = ( + # python function + type(create_autospec), + # instance method + type(ANY.__eq__), +) + + +file_spec = None + + +def _to_stream(read_data): + if isinstance(read_data, bytes): + return io.BytesIO(read_data) + else: + return io.StringIO(read_data) + + +def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read`, `readline` and `readlines` of the + file handle to return. This is an empty string by default. + """ + _read_data = _to_stream(read_data) + _state = [_read_data, None] + + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return _state[0].readlines(*args, **kwargs) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return _state[0].read(*args, **kwargs) + + def _readline_side_effect(*args, **kwargs): + yield from _iter_side_effect() + while True: + yield _state[0].readline(*args, **kwargs) + + def _iter_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _state[0]: + yield line + + def _next_side_effect(): + if handle.readline.return_value is not None: + return handle.readline.return_value + return next(_state[0]) + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + _state[1] = _readline_side_effect() + handle.readline.side_effect = _state[1] + handle.readlines.side_effect = _readlines_side_effect + handle.__iter__.side_effect = _iter_side_effect + handle.__next__.side_effect = _next_side_effect + + def reset_data(*args, **kwargs): + _state[0] = _to_stream(read_data) + if handle.readline.side_effect == _state[1]: + # Only reset the side effect if the user hasn't overridden it. + _state[1] = _readline_side_effect() + handle.readline.side_effect = _state[1] + return DEFAULT + + mock.side_effect = reset_data + mock.return_value = handle + return mock + + +class PropertyMock(Mock): + """ + A mock intended to be used as a property, or other descriptor, on a class. + `PropertyMock` provides `__get__` and `__set__` methods so you can specify + a return value when it is fetched. + + Fetching a `PropertyMock` instance from an object calls the mock, with + no args. Setting it calls the mock with the value being set. + """ + def _get_child_mock(self, /, **kwargs): + return MagicMock(**kwargs) + + def __get__(self, obj, obj_type=None): + return self() + def __set__(self, obj, val): + self(val) + + +def seal(mock): + """Disable the automatic generation of child mocks. + + Given an input Mock, seals it to ensure no further mocks will be generated + when accessing an attribute that was not already defined. + + The operation recursively seals the mock passed in, meaning that + the mock itself, any mocks generated by accessing one of its attributes, + and all assigned mocks without a name or spec will be sealed. + """ + mock._mock_sealed = True + for attr in dir(mock): + try: + m = getattr(mock, attr) + except AttributeError: + continue + if not isinstance(m, NonCallableMock): + continue + if m._mock_new_parent is mock: + seal(m) + + +class _AsyncIterator: + """ + Wraps an iterator in an asynchronous iterator. + """ + def __init__(self, iterator): + self.iterator = iterator + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE + self.__dict__['__code__'] = code_mock + + async def __anext__(self): + try: + return next(self.iterator) + except StopIteration: + pass + raise StopAsyncIteration diff --git a/Monika After Story/game/python-packages/unittest/result.py b/Monika After Story/game/python-packages/unittest/result.py new file mode 100644 index 0000000000..111317b329 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/result.py @@ -0,0 +1,216 @@ +"""Test result object""" + +import io +import sys +import traceback + +from . import util +from functools import wraps + +__unittest = True + +def failfast(method): + @wraps(method) + def inner(self, *args, **kw): + if getattr(self, 'failfast', False): + self.stop() + return method(self, *args, **kw) + return inner + +STDOUT_LINE = '\nStdout:\n%s' +STDERR_LINE = '\nStderr:\n%s' + + +class TestResult(object): + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is the + formatted traceback of the error that occurred. + """ + _previousTestClass = None + _testRunEntered = False + _moduleSetUpFailed = False + def __init__(self, stream=None, descriptions=None, verbosity=None): + self.failfast = False + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.skipped = [] + self.expectedFailures = [] + self.unexpectedSuccesses = [] + self.shouldStop = False + self.buffer = False + self.tb_locals = False + self._stdout_buffer = None + self._stderr_buffer = None + self._original_stdout = sys.stdout + self._original_stderr = sys.stderr + self._mirrorOutput = False + + def printErrors(self): + "Called by TestRunner after test run" + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun += 1 + self._mirrorOutput = False + self._setupStdout() + + def _setupStdout(self): + if self.buffer: + if self._stderr_buffer is None: + self._stderr_buffer = io.StringIO() + self._stdout_buffer = io.StringIO() + sys.stdout = self._stdout_buffer + sys.stderr = self._stderr_buffer + + def startTestRun(self): + """Called once before any tests are executed. + + See startTest for a method called before each test. + """ + + def stopTest(self, test): + """Called when the given test has been run""" + self._restoreStdout() + self._mirrorOutput = False + + def _restoreStdout(self): + if self.buffer: + if self._mirrorOutput: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + self._original_stdout.write(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + self._original_stderr.write(STDERR_LINE % error) + + sys.stdout = self._original_stdout + sys.stderr = self._original_stderr + self._stdout_buffer.seek(0) + self._stdout_buffer.truncate() + self._stderr_buffer.seek(0) + self._stderr_buffer.truncate() + + def stopTestRun(self): + """Called once after all tests are executed. + + See stopTest for a method called after each test. + """ + + @failfast + def addError(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info(). + """ + self.errors.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + @failfast + def addFailure(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info().""" + self.failures.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + def addSubTest(self, test, subtest, err): + """Called at the end of a subtest. + 'err' is None if the subtest ended successfully, otherwise it's a + tuple of values as returned by sys.exc_info(). + """ + # By default, we don't do anything with successful subtests, but + # more sophisticated test results might want to record them. + if err is not None: + if getattr(self, 'failfast', False): + self.stop() + if issubclass(err[0], test.failureException): + errors = self.failures + else: + errors = self.errors + errors.append((subtest, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + def addSuccess(self, test): + "Called when a test has completed successfully" + pass + + def addSkip(self, test, reason): + """Called when a test is skipped.""" + self.skipped.append((test, reason)) + + def addExpectedFailure(self, test, err): + """Called when an expected failure/error occurred.""" + self.expectedFailures.append( + (test, self._exc_info_to_string(err, test))) + + @failfast + def addUnexpectedSuccess(self, test): + """Called when a test was expected to fail, but succeed.""" + self.unexpectedSuccesses.append(test) + + def wasSuccessful(self): + """Tells whether or not this result was a success.""" + # The hasattr check is for test_result's OldResult test. That + # way this method works on objects that lack the attribute. + # (where would such result instances come from? old stored pickles?) + return ((len(self.failures) == len(self.errors) == 0) and + (not hasattr(self, 'unexpectedSuccesses') or + len(self.unexpectedSuccesses) == 0)) + + def stop(self): + """Indicates that the tests should be aborted.""" + self.shouldStop = True + + def _exc_info_to_string(self, err, test): + """Converts a sys.exc_info()-style tuple of values into a string.""" + exctype, value, tb = err + # Skip test runner traceback levels + while tb and self._is_relevant_tb_level(tb): + tb = tb.tb_next + + if exctype is test.failureException: + # Skip assert*() traceback levels + length = self._count_relevant_tb_levels(tb) + else: + length = None + tb_e = traceback.TracebackException( + exctype, value, tb, limit=length, capture_locals=self.tb_locals) + msgLines = list(tb_e.format()) + + if self.buffer: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + msgLines.append(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + msgLines.append(STDERR_LINE % error) + return ''.join(msgLines) + + + def _is_relevant_tb_level(self, tb): + return '__unittest' in tb.tb_frame.f_globals + + def _count_relevant_tb_levels(self, tb): + length = 0 + while tb and not self._is_relevant_tb_level(tb): + length += 1 + tb = tb.tb_next + return length + + def __repr__(self): + return ("<%s run=%i errors=%i failures=%i>" % + (util.strclass(self.__class__), self.testsRun, len(self.errors), + len(self.failures))) diff --git a/Monika After Story/game/python-packages/unittest/runner.py b/Monika After Story/game/python-packages/unittest/runner.py new file mode 100644 index 0000000000..45e7e4c045 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/runner.py @@ -0,0 +1,221 @@ +"""Running tests""" + +import sys +import time +import warnings + +from . import result +from .signals import registerResult + +__unittest = True + + +class _WritelnDecorator(object): + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + if attr in ('stream', '__getstate__'): + raise AttributeError(attr) + return getattr(self.stream,attr) + + def writeln(self, arg=None): + if arg: + self.write(arg) + self.write('\n') # text-mode streams translate to \r\n if needed + + +class TextTestResult(result.TestResult): + """A test result class that can print formatted text results to a stream. + + Used by TextTestRunner. + """ + separator1 = '=' * 70 + separator2 = '-' * 70 + + def __init__(self, stream, descriptions, verbosity): + super(TextTestResult, self).__init__(stream, descriptions, verbosity) + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + + def getDescription(self, test): + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((str(test), doc_first_line)) + else: + return str(test) + + def startTest(self, test): + super(TextTestResult, self).startTest(test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(" ... ") + self.stream.flush() + + def addSuccess(self, test): + super(TextTestResult, self).addSuccess(test) + if self.showAll: + self.stream.writeln("ok") + elif self.dots: + self.stream.write('.') + self.stream.flush() + + def addError(self, test, err): + super(TextTestResult, self).addError(test, err) + if self.showAll: + self.stream.writeln("ERROR") + elif self.dots: + self.stream.write('E') + self.stream.flush() + + def addFailure(self, test, err): + super(TextTestResult, self).addFailure(test, err) + if self.showAll: + self.stream.writeln("FAIL") + elif self.dots: + self.stream.write('F') + self.stream.flush() + + def addSkip(self, test, reason): + super(TextTestResult, self).addSkip(test, reason) + if self.showAll: + self.stream.writeln("skipped {0!r}".format(reason)) + elif self.dots: + self.stream.write("s") + self.stream.flush() + + def addExpectedFailure(self, test, err): + super(TextTestResult, self).addExpectedFailure(test, err) + if self.showAll: + self.stream.writeln("expected failure") + elif self.dots: + self.stream.write("x") + self.stream.flush() + + def addUnexpectedSuccess(self, test): + super(TextTestResult, self).addUnexpectedSuccess(test) + if self.showAll: + self.stream.writeln("unexpected success") + elif self.dots: + self.stream.write("u") + self.stream.flush() + + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln("%s" % err) + + +class TextTestRunner(object): + """A test runner class that displays results in textual form. + + It prints out the names of tests as they are run, errors as they + occur, and a summary of the results at the end of the test run. + """ + resultclass = TextTestResult + + def __init__(self, stream=None, descriptions=True, verbosity=1, + failfast=False, buffer=False, resultclass=None, warnings=None, + *, tb_locals=False): + """Construct a TextTestRunner. + + Subclasses should accept **kwargs to ensure compatibility as the + interface changes. + """ + if stream is None: + stream = sys.stderr + self.stream = _WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + self.failfast = failfast + self.buffer = buffer + self.tb_locals = tb_locals + self.warnings = warnings + if resultclass is not None: + self.resultclass = resultclass + + def _makeResult(self): + return self.resultclass(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + registerResult(result) + result.failfast = self.failfast + result.buffer = self.buffer + result.tb_locals = self.tb_locals + with warnings.catch_warnings(): + if self.warnings: + # if self.warnings is set, use it to filter all the warnings + warnings.simplefilter(self.warnings) + # if the filter is 'default' or 'always', special-case the + # warnings from the deprecated unittest methods to show them + # no more than once per module, because they can be fairly + # noisy. The -Wd and -Wa flags can be used to bypass this + # only when self.warnings is None. + if self.warnings in ['default', 'always']: + warnings.filterwarnings('module', + category=DeprecationWarning, + message=r'Please use assert\w+ instead.') + startTime = time.perf_counter() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + try: + test(result) + finally: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + stopTime = time.perf_counter() + timeTaken = stopTime - startTime + result.printErrors() + if hasattr(result, 'separator2'): + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", timeTaken)) + self.stream.writeln() + + expectedFails = unexpectedSuccesses = skipped = 0 + try: + results = map(len, (result.expectedFailures, + result.unexpectedSuccesses, + result.skipped)) + except AttributeError: + pass + else: + expectedFails, unexpectedSuccesses, skipped = results + + infos = [] + if not result.wasSuccessful(): + self.stream.write("FAILED") + failed, errored = len(result.failures), len(result.errors) + if failed: + infos.append("failures=%d" % failed) + if errored: + infos.append("errors=%d" % errored) + else: + self.stream.write("OK") + if skipped: + infos.append("skipped=%d" % skipped) + if expectedFails: + infos.append("expected failures=%d" % expectedFails) + if unexpectedSuccesses: + infos.append("unexpected successes=%d" % unexpectedSuccesses) + if infos: + self.stream.writeln(" (%s)" % (", ".join(infos),)) + else: + self.stream.write("\n") + return result diff --git a/Monika After Story/game/python-packages/unittest/signals.py b/Monika After Story/game/python-packages/unittest/signals.py new file mode 100644 index 0000000000..e6a5fc5243 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/signals.py @@ -0,0 +1,71 @@ +import signal +import weakref + +from functools import wraps + +__unittest = True + + +class _InterruptHandler(object): + def __init__(self, default_handler): + self.called = False + self.original_handler = default_handler + if isinstance(default_handler, int): + if default_handler == signal.SIG_DFL: + # Pretend it's signal.default_int_handler instead. + default_handler = signal.default_int_handler + elif default_handler == signal.SIG_IGN: + # Not quite the same thing as SIG_IGN, but the closest we + # can make it: do nothing. + def default_handler(unused_signum, unused_frame): + pass + else: + raise TypeError("expected SIGINT signal handler to be " + "signal.SIG_IGN, signal.SIG_DFL, or a " + "callable object") + self.default_handler = default_handler + + def __call__(self, signum, frame): + installed_handler = signal.getsignal(signal.SIGINT) + if installed_handler is not self: + # if we aren't the installed handler, then delegate immediately + # to the default handler + self.default_handler(signum, frame) + + if self.called: + self.default_handler(signum, frame) + self.called = True + for result in _results.keys(): + result.stop() + +_results = weakref.WeakKeyDictionary() +def registerResult(result): + _results[result] = 1 + +def removeResult(result): + return bool(_results.pop(result, None)) + +_interrupt_handler = None +def installHandler(): + global _interrupt_handler + if _interrupt_handler is None: + default_handler = signal.getsignal(signal.SIGINT) + _interrupt_handler = _InterruptHandler(default_handler) + signal.signal(signal.SIGINT, _interrupt_handler) + + +def removeHandler(method=None): + if method is not None: + @wraps(method) + def inner(*args, **kwargs): + initial = signal.getsignal(signal.SIGINT) + removeHandler() + try: + return method(*args, **kwargs) + finally: + signal.signal(signal.SIGINT, initial) + return inner + + global _interrupt_handler + if _interrupt_handler is not None: + signal.signal(signal.SIGINT, _interrupt_handler.original_handler) diff --git a/Monika After Story/game/python-packages/unittest/suite.py b/Monika After Story/game/python-packages/unittest/suite.py new file mode 100644 index 0000000000..41993f9cf6 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/suite.py @@ -0,0 +1,361 @@ +"""TestSuite""" + +import sys + +from . import case +from . import util + +__unittest = True + + +def _call_if_exists(parent, attr): + func = getattr(parent, attr, lambda: None) + func() + + +class BaseTestSuite(object): + """A simple test suite that doesn't provide class or module shared fixtures. + """ + _cleanup = True + + def __init__(self, tests=()): + self._tests = [] + self._removed_tests = 0 + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (util.strclass(self.__class__), list(self)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return list(self) == list(other) + + def __iter__(self): + return iter(self._tests) + + def countTestCases(self): + cases = self._removed_tests + for test in self: + if test: + cases += test.countTestCases() + return cases + + def addTest(self, test): + # sanity checks + if not callable(test): + raise TypeError("{} is not callable".format(repr(test))) + if isinstance(test, type) and issubclass(test, + (case.TestCase, TestSuite)): + raise TypeError("TestCases and TestSuites must be instantiated " + "before passing them to addTest()") + self._tests.append(test) + + def addTests(self, tests): + if isinstance(tests, str): + raise TypeError("tests must be an iterable of tests, not a string") + for test in tests: + self.addTest(test) + + def run(self, result): + for index, test in enumerate(self): + if result.shouldStop: + break + test(result) + if self._cleanup: + self._removeTestAtIndex(index) + return result + + def _removeTestAtIndex(self, index): + """Stop holding a reference to the TestCase at index.""" + try: + test = self._tests[index] + except TypeError: + # support for suite implementations that have overridden self._tests + pass + else: + # Some unittest tests add non TestCase/TestSuite objects to + # the suite. + if hasattr(test, 'countTestCases'): + self._removed_tests += test.countTestCases() + self._tests[index] = None + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self: + test.debug() + + +class TestSuite(BaseTestSuite): + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + + def run(self, result, debug=False): + topLevel = False + if getattr(result, '_testRunEntered', False) is False: + result._testRunEntered = topLevel = True + + for index, test in enumerate(self): + if result.shouldStop: + break + + if _isnotsuite(test): + self._tearDownPreviousClass(test, result) + self._handleModuleFixture(test, result) + self._handleClassSetUp(test, result) + result._previousTestClass = test.__class__ + + if (getattr(test.__class__, '_classSetupFailed', False) or + getattr(result, '_moduleSetUpFailed', False)): + continue + + if not debug: + test(result) + else: + test.debug() + + if self._cleanup: + self._removeTestAtIndex(index) + + if topLevel: + self._tearDownPreviousClass(None, result) + self._handleModuleTearDown(result) + result._testRunEntered = False + return result + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + debug = _DebugResult() + self.run(debug, True) + + ################################ + + def _handleClassSetUp(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if result._moduleSetUpFailed: + return + if getattr(currentClass, "__unittest_skip__", False): + return + + try: + currentClass._classSetupFailed = False + except TypeError: + # test may actually be a function + # so its class will be a builtin-type + pass + + setUpClass = getattr(currentClass, 'setUpClass', None) + if setUpClass is not None: + _call_if_exists(result, '_setupStdout') + try: + setUpClass() + except Exception as e: + if isinstance(result, _DebugResult): + raise + currentClass._classSetupFailed = True + className = util.strclass(currentClass) + self._createClassOrModuleLevelException(result, e, + 'setUpClass', + className) + finally: + _call_if_exists(result, '_restoreStdout') + if currentClass._classSetupFailed is True: + currentClass.doClassCleanups() + if len(currentClass.tearDown_exceptions) > 0: + for exc in currentClass.tearDown_exceptions: + self._createClassOrModuleLevelException( + result, exc[1], 'setUpClass', className, + info=exc) + + def _get_previous_module(self, result): + previousModule = None + previousClass = getattr(result, '_previousTestClass', None) + if previousClass is not None: + previousModule = previousClass.__module__ + return previousModule + + + def _handleModuleFixture(self, test, result): + previousModule = self._get_previous_module(result) + currentModule = test.__class__.__module__ + if currentModule == previousModule: + return + + self._handleModuleTearDown(result) + + + result._moduleSetUpFailed = False + try: + module = sys.modules[currentModule] + except KeyError: + return + setUpModule = getattr(module, 'setUpModule', None) + if setUpModule is not None: + _call_if_exists(result, '_setupStdout') + try: + setUpModule() + except Exception as e: + try: + case.doModuleCleanups() + except Exception as exc: + self._createClassOrModuleLevelException(result, exc, + 'setUpModule', + currentModule) + if isinstance(result, _DebugResult): + raise + result._moduleSetUpFailed = True + self._createClassOrModuleLevelException(result, e, + 'setUpModule', + currentModule) + finally: + _call_if_exists(result, '_restoreStdout') + + def _createClassOrModuleLevelException(self, result, exc, method_name, + parent, info=None): + errorName = f'{method_name} ({parent})' + self._addClassOrModuleLevelException(result, exc, errorName, info) + + def _addClassOrModuleLevelException(self, result, exception, errorName, + info=None): + error = _ErrorHolder(errorName) + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None and isinstance(exception, case.SkipTest): + addSkip(error, str(exception)) + else: + if not info: + result.addError(error, sys.exc_info()) + else: + result.addError(error, info) + + def _handleModuleTearDown(self, result): + previousModule = self._get_previous_module(result) + if previousModule is None: + return + if result._moduleSetUpFailed: + return + + try: + module = sys.modules[previousModule] + except KeyError: + return + + tearDownModule = getattr(module, 'tearDownModule', None) + if tearDownModule is not None: + _call_if_exists(result, '_setupStdout') + try: + tearDownModule() + except Exception as e: + if isinstance(result, _DebugResult): + raise + self._createClassOrModuleLevelException(result, e, + 'tearDownModule', + previousModule) + finally: + _call_if_exists(result, '_restoreStdout') + try: + case.doModuleCleanups() + except Exception as e: + self._createClassOrModuleLevelException(result, e, + 'tearDownModule', + previousModule) + + def _tearDownPreviousClass(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if getattr(previousClass, '_classSetupFailed', False): + return + if getattr(result, '_moduleSetUpFailed', False): + return + if getattr(previousClass, "__unittest_skip__", False): + return + + tearDownClass = getattr(previousClass, 'tearDownClass', None) + if tearDownClass is not None: + _call_if_exists(result, '_setupStdout') + try: + tearDownClass() + except Exception as e: + if isinstance(result, _DebugResult): + raise + className = util.strclass(previousClass) + self._createClassOrModuleLevelException(result, e, + 'tearDownClass', + className) + finally: + _call_if_exists(result, '_restoreStdout') + previousClass.doClassCleanups() + if len(previousClass.tearDown_exceptions) > 0: + for exc in previousClass.tearDown_exceptions: + className = util.strclass(previousClass) + self._createClassOrModuleLevelException(result, exc[1], + 'tearDownClass', + className, + info=exc) + + +class _ErrorHolder(object): + """ + Placeholder for a TestCase inside a result. As far as a TestResult + is concerned, this looks exactly like a unit test. Used to insert + arbitrary errors into a test suite run. + """ + # Inspired by the ErrorHolder from Twisted: + # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py + + # attribute used by TestResult._exc_info_to_string + failureException = None + + def __init__(self, description): + self.description = description + + def id(self): + return self.description + + def shortDescription(self): + return None + + def __repr__(self): + return "" % (self.description,) + + def __str__(self): + return self.id() + + def run(self, result): + # could call result.addError(...) - but this test-like object + # shouldn't be run anyway + pass + + def __call__(self, result): + return self.run(result) + + def countTestCases(self): + return 0 + +def _isnotsuite(test): + "A crude way to tell apart testcases and suites with duck-typing" + try: + iter(test) + except TypeError: + return True + return False + + +class _DebugResult(object): + "Used by the TestSuite to hold previous class when running in debug." + _previousTestClass = None + _moduleSetUpFailed = False + shouldStop = False diff --git a/Monika After Story/game/python-packages/unittest/test/__init__.py b/Monika After Story/game/python-packages/unittest/test/__init__.py new file mode 100644 index 0000000000..cdae8a7442 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/__init__.py @@ -0,0 +1,22 @@ +import os +import sys +import unittest + + +here = os.path.dirname(__file__) +loader = unittest.defaultTestLoader + +def suite(): + suite = unittest.TestSuite() + for fn in os.listdir(here): + if fn.startswith("test") and fn.endswith(".py"): + modname = "unittest.test." + fn[:-3] + __import__(modname) + module = sys.modules[modname] + suite.addTest(loader.loadTestsFromModule(module)) + suite.addTest(loader.loadTestsFromName('unittest.test.testmock')) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="suite") diff --git a/Monika After Story/game/python-packages/unittest/test/__main__.py b/Monika After Story/game/python-packages/unittest/test/__main__.py new file mode 100644 index 0000000000..44d0591e84 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/__main__.py @@ -0,0 +1,18 @@ +import os +import unittest + + +def load_tests(loader, standard_tests, pattern): + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + pattern = pattern or "test_*.py" + # We are inside unittest.test, so the top-level is two notches up + top_level_dir = os.path.dirname(os.path.dirname(this_dir)) + package_tests = loader.discover(start_dir=this_dir, pattern=pattern, + top_level_dir=top_level_dir) + standard_tests.addTests(package_tests) + return standard_tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/_test_warnings.py b/Monika After Story/game/python-packages/unittest/test/_test_warnings.py new file mode 100644 index 0000000000..5cbfb532ad --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/_test_warnings.py @@ -0,0 +1,73 @@ +# helper module for test_runner.Test_TextTestRunner.test_warnings + +""" +This module has a number of tests that raise different kinds of warnings. +When the tests are run, the warnings are caught and their messages are printed +to stdout. This module also accepts an arg that is then passed to +unittest.main to affect the behavior of warnings. +Test_TextTestRunner.test_warnings executes this script with different +combinations of warnings args and -W flags and check that the output is correct. +See #10535. +""" + +import sys +import unittest +import warnings + +def warnfun(): + warnings.warn('rw', RuntimeWarning) + +class TestWarnings(unittest.TestCase): + # unittest warnings will be printed at most once per type (max one message + # for the fail* methods, and one for the assert* methods) + def test_assert(self): + self.assertEquals(2+2, 4) + self.assertEquals(2*2, 4) + self.assertEquals(2**2, 4) + + def test_fail(self): + self.failUnless(1) + self.failUnless(True) + + def test_other_unittest(self): + self.assertAlmostEqual(2+2, 4) + self.assertNotAlmostEqual(4+4, 2) + + # these warnings are normally silenced, but they are printed in unittest + def test_deprecation(self): + warnings.warn('dw', DeprecationWarning) + warnings.warn('dw', DeprecationWarning) + warnings.warn('dw', DeprecationWarning) + + def test_import(self): + warnings.warn('iw', ImportWarning) + warnings.warn('iw', ImportWarning) + warnings.warn('iw', ImportWarning) + + # user warnings should always be printed + def test_warning(self): + warnings.warn('uw') + warnings.warn('uw') + warnings.warn('uw') + + # these warnings come from the same place; they will be printed + # only once by default or three times if the 'always' filter is used + def test_function(self): + + warnfun() + warnfun() + warnfun() + + + +if __name__ == '__main__': + with warnings.catch_warnings(record=True) as ws: + # if an arg is provided pass it to unittest.main as 'warnings' + if len(sys.argv) == 2: + unittest.main(exit=False, warnings=sys.argv.pop()) + else: + unittest.main(exit=False) + + # print all the warning messages collected + for w in ws: + print(w.message) diff --git a/Monika After Story/game/python-packages/unittest/test/dummy.py b/Monika After Story/game/python-packages/unittest/test/dummy.py new file mode 100644 index 0000000000..e4f14e4035 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/dummy.py @@ -0,0 +1 @@ +# Empty module for testing the loading of modules diff --git a/Monika After Story/game/python-packages/unittest/test/support.py b/Monika After Story/game/python-packages/unittest/test/support.py new file mode 100644 index 0000000000..529265304f --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/support.py @@ -0,0 +1,138 @@ +import unittest + + +class TestEquality(object): + """Used as a mixin for TestCase""" + + # Check for a valid __eq__ implementation + def test_eq(self): + for obj_1, obj_2 in self.eq_pairs: + self.assertEqual(obj_1, obj_2) + self.assertEqual(obj_2, obj_1) + + # Check for a valid __ne__ implementation + def test_ne(self): + for obj_1, obj_2 in self.ne_pairs: + self.assertNotEqual(obj_1, obj_2) + self.assertNotEqual(obj_2, obj_1) + +class TestHashing(object): + """Used as a mixin for TestCase""" + + # Check for a valid __hash__ implementation + def test_hash(self): + for obj_1, obj_2 in self.eq_pairs: + try: + if not hash(obj_1) == hash(obj_2): + self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) + except Exception as e: + self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) + + for obj_1, obj_2 in self.ne_pairs: + try: + if hash(obj_1) == hash(obj_2): + self.fail("%s and %s hash equal, but shouldn't" % + (obj_1, obj_2)) + except Exception as e: + self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) + + +class _BaseLoggingResult(unittest.TestResult): + def __init__(self, log): + self._events = log + super().__init__() + + def startTest(self, test): + self._events.append('startTest') + super().startTest(test) + + def startTestRun(self): + self._events.append('startTestRun') + super().startTestRun() + + def stopTest(self, test): + self._events.append('stopTest') + super().stopTest(test) + + def stopTestRun(self): + self._events.append('stopTestRun') + super().stopTestRun() + + def addFailure(self, *args): + self._events.append('addFailure') + super().addFailure(*args) + + def addSuccess(self, *args): + self._events.append('addSuccess') + super().addSuccess(*args) + + def addError(self, *args): + self._events.append('addError') + super().addError(*args) + + def addSkip(self, *args): + self._events.append('addSkip') + super().addSkip(*args) + + def addExpectedFailure(self, *args): + self._events.append('addExpectedFailure') + super().addExpectedFailure(*args) + + def addUnexpectedSuccess(self, *args): + self._events.append('addUnexpectedSuccess') + super().addUnexpectedSuccess(*args) + + +class LegacyLoggingResult(_BaseLoggingResult): + """ + A legacy TestResult implementation, without an addSubTest method, + which records its method calls. + """ + + @property + def addSubTest(self): + raise AttributeError + + +class LoggingResult(_BaseLoggingResult): + """ + A TestResult implementation which records its method calls. + """ + + def addSubTest(self, test, subtest, err): + if err is None: + self._events.append('addSubTestSuccess') + else: + self._events.append('addSubTestFailure') + super().addSubTest(test, subtest, err) + + +class ResultWithNoStartTestRunStopTestRun(object): + """An object honouring TestResult before startTestRun/stopTestRun.""" + + def __init__(self): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.skipped = [] + self.expectedFailures = [] + self.unexpectedSuccesses = [] + self.shouldStop = False + + def startTest(self, test): + pass + + def stopTest(self, test): + pass + + def addError(self, test): + pass + + def addFailure(self, test): + pass + + def addSuccess(self, test): + pass + + def wasSuccessful(self): + return True diff --git a/Monika After Story/game/python-packages/unittest/test/test_assertions.py b/Monika After Story/game/python-packages/unittest/test/test_assertions.py new file mode 100644 index 0000000000..f5e64d68e7 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_assertions.py @@ -0,0 +1,413 @@ +import datetime +import warnings +import weakref +import unittest +from itertools import product + + +class Test_Assertions(unittest.TestCase): + def test_AlmostEqual(self): + self.assertAlmostEqual(1.00000001, 1.0) + self.assertNotAlmostEqual(1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 1.00000001, 1.0) + + self.assertAlmostEqual(1.1, 1.0, places=0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.1, 1.0, places=1) + + self.assertAlmostEqual(0, .1+.1j, places=0) + self.assertNotAlmostEqual(0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 0, .1+.1j, places=0) + + self.assertAlmostEqual(float('inf'), float('inf')) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + float('inf'), float('inf')) + + def test_AmostEqualWithDelta(self): + self.assertAlmostEqual(1.1, 1.0, delta=0.5) + self.assertAlmostEqual(1.0, 1.1, delta=0.5) + self.assertNotAlmostEqual(1.1, 1.0, delta=0.05) + self.assertNotAlmostEqual(1.0, 1.1, delta=0.05) + + self.assertAlmostEqual(1.0, 1.0, delta=0.5) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + 1.0, 1.0, delta=0.5) + + self.assertRaises(self.failureException, self.assertAlmostEqual, + 1.1, 1.0, delta=0.05) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + 1.1, 1.0, delta=0.5) + + self.assertRaises(TypeError, self.assertAlmostEqual, + 1.1, 1.0, places=2, delta=2) + self.assertRaises(TypeError, self.assertNotAlmostEqual, + 1.1, 1.0, places=2, delta=2) + + first = datetime.datetime.now() + second = first + datetime.timedelta(seconds=10) + self.assertAlmostEqual(first, second, + delta=datetime.timedelta(seconds=20)) + self.assertNotAlmostEqual(first, second, + delta=datetime.timedelta(seconds=5)) + + def test_assertRaises(self): + def _raise(e): + raise e + self.assertRaises(KeyError, _raise, KeyError) + self.assertRaises(KeyError, _raise, KeyError("key")) + try: + self.assertRaises(KeyError, lambda: None) + except self.failureException as e: + self.assertIn("KeyError not raised", str(e)) + else: + self.fail("assertRaises() didn't fail") + try: + self.assertRaises(KeyError, _raise, ValueError) + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + with self.assertRaises(KeyError) as cm: + try: + raise KeyError + except Exception as e: + exc = e + raise + self.assertIs(cm.exception, exc) + + with self.assertRaises(KeyError): + raise KeyError("key") + try: + with self.assertRaises(KeyError): + pass + except self.failureException as e: + self.assertIn("KeyError not raised", str(e)) + else: + self.fail("assertRaises() didn't fail") + try: + with self.assertRaises(KeyError): + raise ValueError + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + + def test_assertRaises_frames_survival(self): + # Issue #9815: assertRaises should avoid keeping local variables + # in a traceback alive. + class A: + pass + wr = None + + class Foo(unittest.TestCase): + + def foo(self): + nonlocal wr + a = A() + wr = weakref.ref(a) + try: + raise OSError + except OSError: + raise ValueError + + def test_functional(self): + self.assertRaises(ValueError, self.foo) + + def test_with(self): + with self.assertRaises(ValueError): + self.foo() + + Foo("test_functional").run() + self.assertIsNone(wr()) + Foo("test_with").run() + self.assertIsNone(wr()) + + def testAssertNotRegex(self): + self.assertNotRegex('Ala ma kota', r'r+') + try: + self.assertNotRegex('Ala ma kota', r'k.t', 'Message') + except self.failureException as e: + self.assertIn('Message', e.args[0]) + else: + self.fail('assertNotRegex should have failed.') + + +class TestLongMessage(unittest.TestCase): + """Test that the individual asserts honour longMessage. + This actually tests all the message behaviour for + asserts that use longMessage.""" + + def setUp(self): + class TestableTestFalse(unittest.TestCase): + longMessage = False + failureException = self.failureException + + def testTest(self): + pass + + class TestableTestTrue(unittest.TestCase): + longMessage = True + failureException = self.failureException + + def testTest(self): + pass + + self.testableTrue = TestableTestTrue('testTest') + self.testableFalse = TestableTestFalse('testTest') + + def testDefault(self): + self.assertTrue(unittest.TestCase.longMessage) + + def test_formatMsg(self): + self.assertEqual(self.testableFalse._formatMessage(None, "foo"), "foo") + self.assertEqual(self.testableFalse._formatMessage("foo", "bar"), "foo") + + self.assertEqual(self.testableTrue._formatMessage(None, "foo"), "foo") + self.assertEqual(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") + + # This blows up if _formatMessage uses string concatenation + self.testableTrue._formatMessage(object(), 'foo') + + def test_formatMessage_unicode_error(self): + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing msg + self.testableTrue._formatMessage(one, '\uFFFD') + + def assertMessages(self, methodName, args, errors): + """ + Check that methodName(*args) raises the correct error messages. + errors should be a list of 4 regex that match the error when: + 1) longMessage = False and no msg passed; + 2) longMessage = False and msg passed; + 3) longMessage = True and no msg passed; + 4) longMessage = True and msg passed; + """ + def getMethod(i): + useTestableFalse = i < 2 + if useTestableFalse: + test = self.testableFalse + else: + test = self.testableTrue + return getattr(test, methodName) + + for i, expected_regex in enumerate(errors): + testMethod = getMethod(i) + kwargs = {} + withMsg = i % 2 + if withMsg: + kwargs = {"msg": "oops"} + + with self.assertRaisesRegex(self.failureException, + expected_regex=expected_regex): + testMethod(*args, **kwargs) + + def testAssertTrue(self): + self.assertMessages('assertTrue', (False,), + ["^False is not true$", "^oops$", "^False is not true$", + "^False is not true : oops$"]) + + def testAssertFalse(self): + self.assertMessages('assertFalse', (True,), + ["^True is not false$", "^oops$", "^True is not false$", + "^True is not false : oops$"]) + + def testNotEqual(self): + self.assertMessages('assertNotEqual', (1, 1), + ["^1 == 1$", "^oops$", "^1 == 1$", + "^1 == 1 : oops$"]) + + def testAlmostEqual(self): + self.assertMessages( + 'assertAlmostEqual', (1, 2), + [r"^1 != 2 within 7 places \(1 difference\)$", "^oops$", + r"^1 != 2 within 7 places \(1 difference\)$", + r"^1 != 2 within 7 places \(1 difference\) : oops$"]) + + def testNotAlmostEqual(self): + self.assertMessages('assertNotAlmostEqual', (1, 1), + ["^1 == 1 within 7 places$", "^oops$", + "^1 == 1 within 7 places$", "^1 == 1 within 7 places : oops$"]) + + def test_baseAssertEqual(self): + self.assertMessages('_baseAssertEqual', (1, 2), + ["^1 != 2$", "^oops$", "^1 != 2$", "^1 != 2 : oops$"]) + + def testAssertSequenceEqual(self): + # Error messages are multiline so not testing on full message + # assertTupleEqual and assertListEqual delegate to this method + self.assertMessages('assertSequenceEqual', ([], [None]), + [r"\+ \[None\]$", "^oops$", r"\+ \[None\]$", + r"\+ \[None\] : oops$"]) + + def testAssertSetEqual(self): + self.assertMessages('assertSetEqual', (set(), set([None])), + ["None$", "^oops$", "None$", + "None : oops$"]) + + def testAssertIn(self): + self.assertMessages('assertIn', (None, []), + [r'^None not found in \[\]$', "^oops$", + r'^None not found in \[\]$', + r'^None not found in \[\] : oops$']) + + def testAssertNotIn(self): + self.assertMessages('assertNotIn', (None, [None]), + [r'^None unexpectedly found in \[None\]$', "^oops$", + r'^None unexpectedly found in \[None\]$', + r'^None unexpectedly found in \[None\] : oops$']) + + def testAssertDictEqual(self): + self.assertMessages('assertDictEqual', ({}, {'key': 'value'}), + [r"\+ \{'key': 'value'\}$", "^oops$", + r"\+ \{'key': 'value'\}$", + r"\+ \{'key': 'value'\} : oops$"]) + + def testAssertDictContainsSubset(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + + self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}), + ["^Missing: 'key'$", "^oops$", + "^Missing: 'key'$", + "^Missing: 'key' : oops$"]) + + def testAssertMultiLineEqual(self): + self.assertMessages('assertMultiLineEqual', ("", "foo"), + [r"\+ foo$", "^oops$", + r"\+ foo$", + r"\+ foo : oops$"]) + + def testAssertLess(self): + self.assertMessages('assertLess', (2, 1), + ["^2 not less than 1$", "^oops$", + "^2 not less than 1$", "^2 not less than 1 : oops$"]) + + def testAssertLessEqual(self): + self.assertMessages('assertLessEqual', (2, 1), + ["^2 not less than or equal to 1$", "^oops$", + "^2 not less than or equal to 1$", + "^2 not less than or equal to 1 : oops$"]) + + def testAssertGreater(self): + self.assertMessages('assertGreater', (1, 2), + ["^1 not greater than 2$", "^oops$", + "^1 not greater than 2$", + "^1 not greater than 2 : oops$"]) + + def testAssertGreaterEqual(self): + self.assertMessages('assertGreaterEqual', (1, 2), + ["^1 not greater than or equal to 2$", "^oops$", + "^1 not greater than or equal to 2$", + "^1 not greater than or equal to 2 : oops$"]) + + def testAssertIsNone(self): + self.assertMessages('assertIsNone', ('not None',), + ["^'not None' is not None$", "^oops$", + "^'not None' is not None$", + "^'not None' is not None : oops$"]) + + def testAssertIsNotNone(self): + self.assertMessages('assertIsNotNone', (None,), + ["^unexpectedly None$", "^oops$", + "^unexpectedly None$", + "^unexpectedly None : oops$"]) + + def testAssertIs(self): + self.assertMessages('assertIs', (None, 'foo'), + ["^None is not 'foo'$", "^oops$", + "^None is not 'foo'$", + "^None is not 'foo' : oops$"]) + + def testAssertIsNot(self): + self.assertMessages('assertIsNot', (None, None), + ["^unexpectedly identical: None$", "^oops$", + "^unexpectedly identical: None$", + "^unexpectedly identical: None : oops$"]) + + def testAssertRegex(self): + self.assertMessages('assertRegex', ('foo', 'bar'), + ["^Regex didn't match:", + "^oops$", + "^Regex didn't match:", + "^Regex didn't match: (.*) : oops$"]) + + def testAssertNotRegex(self): + self.assertMessages('assertNotRegex', ('foo', 'foo'), + ["^Regex matched:", + "^oops$", + "^Regex matched:", + "^Regex matched: (.*) : oops$"]) + + + def assertMessagesCM(self, methodName, args, func, errors): + """ + Check that the correct error messages are raised while executing: + with method(*args): + func() + *errors* should be a list of 4 regex that match the error when: + 1) longMessage = False and no msg passed; + 2) longMessage = False and msg passed; + 3) longMessage = True and no msg passed; + 4) longMessage = True and msg passed; + """ + p = product((self.testableFalse, self.testableTrue), + ({}, {"msg": "oops"})) + for (cls, kwargs), err in zip(p, errors): + method = getattr(cls, methodName) + with self.assertRaisesRegex(cls.failureException, err): + with method(*args, **kwargs) as cm: + func() + + def testAssertRaises(self): + self.assertMessagesCM('assertRaises', (TypeError,), lambda: None, + ['^TypeError not raised$', '^oops$', + '^TypeError not raised$', + '^TypeError not raised : oops$']) + + def testAssertRaisesRegex(self): + # test error not raised + self.assertMessagesCM('assertRaisesRegex', (TypeError, 'unused regex'), + lambda: None, + ['^TypeError not raised$', '^oops$', + '^TypeError not raised$', + '^TypeError not raised : oops$']) + # test error raised but with wrong message + def raise_wrong_message(): + raise TypeError('foo') + self.assertMessagesCM('assertRaisesRegex', (TypeError, 'regex'), + raise_wrong_message, + ['^"regex" does not match "foo"$', '^oops$', + '^"regex" does not match "foo"$', + '^"regex" does not match "foo" : oops$']) + + def testAssertWarns(self): + self.assertMessagesCM('assertWarns', (UserWarning,), lambda: None, + ['^UserWarning not triggered$', '^oops$', + '^UserWarning not triggered$', + '^UserWarning not triggered : oops$']) + + def testAssertWarnsRegex(self): + # test error not raised + self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'unused regex'), + lambda: None, + ['^UserWarning not triggered$', '^oops$', + '^UserWarning not triggered$', + '^UserWarning not triggered : oops$']) + # test warning raised but with wrong message + def raise_wrong_message(): + warnings.warn('foo') + self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'regex'), + raise_wrong_message, + ['^"regex" does not match "foo"$', '^oops$', + '^"regex" does not match "foo"$', + '^"regex" does not match "foo" : oops$']) + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_async_case.py b/Monika After Story/game/python-packages/unittest/test/test_async_case.py new file mode 100644 index 0000000000..d01864b693 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_async_case.py @@ -0,0 +1,222 @@ +import asyncio +import unittest + + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + + +class TestAsyncCase(unittest.TestCase): + def test_full_cycle(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.assertEqual(events, []) + events.append('setUp') + + async def asyncSetUp(self): + self.assertEqual(events, ['setUp']) + events.append('asyncSetUp') + + async def test_func(self): + self.assertEqual(events, ['setUp', + 'asyncSetUp']) + events.append('test') + self.addAsyncCleanup(self.on_cleanup) + + async def asyncTearDown(self): + self.assertEqual(events, ['setUp', + 'asyncSetUp', + 'test']) + events.append('asyncTearDown') + + def tearDown(self): + self.assertEqual(events, ['setUp', + 'asyncSetUp', + 'test', + 'asyncTearDown']) + events.append('tearDown') + + async def on_cleanup(self): + self.assertEqual(events, ['setUp', + 'asyncSetUp', + 'test', + 'asyncTearDown', + 'tearDown']) + events.append('cleanup') + + test = Test("test_func") + test.run() + self.assertEqual(events, ['setUp', + 'asyncSetUp', + 'test', + 'asyncTearDown', + 'tearDown', + 'cleanup']) + + def test_exception_in_setup(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + events.append('asyncSetUp') + raise Exception() + + async def test_func(self): + events.append('test') + self.addAsyncCleanup(self.on_cleanup) + + async def asyncTearDown(self): + events.append('asyncTearDown') + + async def on_cleanup(self): + events.append('cleanup') + + + test = Test("test_func") + test.run() + self.assertEqual(events, ['asyncSetUp']) + + def test_exception_in_test(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + events.append('asyncSetUp') + + async def test_func(self): + events.append('test') + raise Exception() + self.addAsyncCleanup(self.on_cleanup) + + async def asyncTearDown(self): + events.append('asyncTearDown') + + async def on_cleanup(self): + events.append('cleanup') + + test = Test("test_func") + test.run() + self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown']) + + def test_exception_in_test_after_adding_cleanup(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + events.append('asyncSetUp') + + async def test_func(self): + events.append('test') + self.addAsyncCleanup(self.on_cleanup) + raise Exception() + + async def asyncTearDown(self): + events.append('asyncTearDown') + + async def on_cleanup(self): + events.append('cleanup') + + test = Test("test_func") + test.run() + self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) + + def test_exception_in_tear_down(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + events.append('asyncSetUp') + + async def test_func(self): + events.append('test') + self.addAsyncCleanup(self.on_cleanup) + + async def asyncTearDown(self): + events.append('asyncTearDown') + raise Exception() + + async def on_cleanup(self): + events.append('cleanup') + + test = Test("test_func") + test.run() + self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) + + + def test_exception_in_tear_clean_up(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + events.append('asyncSetUp') + + async def test_func(self): + events.append('test') + self.addAsyncCleanup(self.on_cleanup) + + async def asyncTearDown(self): + events.append('asyncTearDown') + + async def on_cleanup(self): + events.append('cleanup') + raise Exception() + + test = Test("test_func") + test.run() + self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) + + def test_cleanups_interleave_order(self): + events = [] + + class Test(unittest.IsolatedAsyncioTestCase): + async def test_func(self): + self.addAsyncCleanup(self.on_sync_cleanup, 1) + self.addAsyncCleanup(self.on_async_cleanup, 2) + self.addAsyncCleanup(self.on_sync_cleanup, 3) + self.addAsyncCleanup(self.on_async_cleanup, 4) + + async def on_sync_cleanup(self, val): + events.append(f'sync_cleanup {val}') + + async def on_async_cleanup(self, val): + events.append(f'async_cleanup {val}') + + test = Test("test_func") + test.run() + self.assertEqual(events, ['async_cleanup 4', + 'sync_cleanup 3', + 'async_cleanup 2', + 'sync_cleanup 1']) + + def test_base_exception_from_async_method(self): + events = [] + class Test(unittest.IsolatedAsyncioTestCase): + async def test_base(self): + events.append("test_base") + raise BaseException() + events.append("not it") + + async def test_no_err(self): + events.append("test_no_err") + + async def test_cancel(self): + raise asyncio.CancelledError() + + test = Test("test_base") + output = test.run() + self.assertFalse(output.wasSuccessful()) + + test = Test("test_no_err") + test.run() + self.assertEqual(events, ['test_base', 'test_no_err']) + + test = Test("test_cancel") + output = test.run() + self.assertFalse(output.wasSuccessful()) + + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_break.py b/Monika After Story/game/python-packages/unittest/test/test_break.py new file mode 100644 index 0000000000..eebd2b610c --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_break.py @@ -0,0 +1,280 @@ +import gc +import io +import os +import sys +import signal +import weakref + +import unittest + + +@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") +@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") +class TestBreak(unittest.TestCase): + int_handler = None + + def setUp(self): + self._default_handler = signal.getsignal(signal.SIGINT) + if self.int_handler is not None: + signal.signal(signal.SIGINT, self.int_handler) + + def tearDown(self): + signal.signal(signal.SIGINT, self._default_handler) + unittest.signals._results = weakref.WeakKeyDictionary() + unittest.signals._interrupt_handler = None + + + def testInstallHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest.installHandler() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(unittest.signals._interrupt_handler.called) + + def testRegisterResult(self): + result = unittest.TestResult() + self.assertNotIn(result, unittest.signals._results) + + unittest.registerResult(result) + try: + self.assertIn(result, unittest.signals._results) + finally: + unittest.removeResult(result) + + def testInterruptCaught(self): + default_handler = signal.getsignal(signal.SIGINT) + + result = unittest.TestResult() + unittest.installHandler() + unittest.registerResult(result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + self.assertTrue(result.breakCaught) + + + def testSecondInterrupt(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") + result = unittest.TestResult() + unittest.installHandler() + unittest.registerResult(result) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + os.kill(pid, signal.SIGINT) + self.fail("Second KeyboardInterrupt not raised") + + try: + test(result) + except KeyboardInterrupt: + pass + else: + self.fail("Second KeyboardInterrupt not raised") + self.assertTrue(result.breakCaught) + + + def testTwoResults(self): + unittest.installHandler() + + result = unittest.TestResult() + unittest.registerResult(result) + new_handler = signal.getsignal(signal.SIGINT) + + result2 = unittest.TestResult() + unittest.registerResult(result2) + self.assertEqual(signal.getsignal(signal.SIGINT), new_handler) + + result3 = unittest.TestResult() + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(result.shouldStop) + self.assertTrue(result2.shouldStop) + self.assertFalse(result3.shouldStop) + + + def testHandlerReplacedButCalled(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") + # If our handler has been replaced (is no longer installed) but is + # called by the *new* handler, then it isn't safe to delay the + # SIGINT and we should immediately delegate to the default handler + unittest.installHandler() + + handler = signal.getsignal(signal.SIGINT) + def new_handler(frame, signum): + handler(frame, signum) + signal.signal(signal.SIGINT, new_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + else: + self.fail("replaced but delegated handler doesn't raise interrupt") + + def testRunner(self): + # Creating a TextTestRunner with the appropriate argument should + # register the TextTestResult it creates + runner = unittest.TextTestRunner(stream=io.StringIO()) + + result = runner.run(unittest.TestSuite()) + self.assertIn(result, unittest.signals._results) + + def testWeakReferences(self): + # Calling registerResult on a result should not keep it alive + result = unittest.TestResult() + unittest.registerResult(result) + + ref = weakref.ref(result) + del result + + # For non-reference counting implementations + gc.collect();gc.collect() + self.assertIsNone(ref()) + + + def testRemoveResult(self): + result = unittest.TestResult() + unittest.registerResult(result) + + unittest.installHandler() + self.assertTrue(unittest.removeResult(result)) + + # Should this raise an error instead? + self.assertFalse(unittest.removeResult(unittest.TestResult())) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + + self.assertFalse(result.shouldStop) + + def testMainInstallsHandler(self): + failfast = object() + test = object() + verbosity = object() + result = object() + default_handler = signal.getsignal(signal.SIGINT) + + class FakeRunner(object): + initArgs = [] + runArgs = [] + def __init__(self, *args, **kwargs): + self.initArgs.append((args, kwargs)) + def run(self, test): + self.runArgs.append(test) + return result + + class Program(unittest.TestProgram): + def __init__(self, catchbreak): + self.exit = False + self.verbosity = verbosity + self.failfast = failfast + self.catchbreak = catchbreak + self.tb_locals = False + self.testRunner = FakeRunner + self.test = test + self.result = None + + p = Program(False) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, + 'verbosity': verbosity, + 'failfast': failfast, + 'tb_locals': False, + 'warnings': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + FakeRunner.initArgs = [] + FakeRunner.runArgs = [] + p = Program(True) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, + 'verbosity': verbosity, + 'failfast': failfast, + 'tb_locals': False, + 'warnings': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + def testRemoveHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest.installHandler() + unittest.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + # check that calling removeHandler multiple times has no ill-effect + unittest.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + def testRemoveHandlerAsDecorator(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest.installHandler() + + @unittest.removeHandler + def test(): + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + test() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + +@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") +@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") +class TestBreakDefaultIntHandler(TestBreak): + int_handler = signal.default_int_handler + +@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") +@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") +class TestBreakSignalIgnored(TestBreak): + int_handler = signal.SIG_IGN + +@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") +@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") +class TestBreakSignalDefault(TestBreak): + int_handler = signal.SIG_DFL + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_case.py b/Monika After Story/game/python-packages/unittest/test/test_case.py new file mode 100644 index 0000000000..f855c4dc00 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_case.py @@ -0,0 +1,1852 @@ +import contextlib +import difflib +import pprint +import pickle +import re +import sys +import logging +import warnings +import weakref +import inspect + +from copy import deepcopy +from test import support + +import unittest + +from unittest.test.support import ( + TestEquality, TestHashing, LoggingResult, LegacyLoggingResult, + ResultWithNoStartTestRunStopTestRun +) +from test.support import captured_stderr + + +log_foo = logging.getLogger('foo') +log_foobar = logging.getLogger('foo.bar') +log_quux = logging.getLogger('quux') + + +class Test(object): + "Keep these TestCase classes out of the main namespace" + + class Foo(unittest.TestCase): + def runTest(self): pass + def test1(self): pass + + class Bar(Foo): + def test2(self): pass + + class LoggingTestCase(unittest.TestCase): + """A test case which logs its calls.""" + + def __init__(self, events): + super(Test.LoggingTestCase, self).__init__('test') + self.events = events + + def setUp(self): + self.events.append('setUp') + + def test(self): + self.events.append('test') + + def tearDown(self): + self.events.append('tearDown') + + +class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): + + ### Set up attributes used by inherited tests + ################################################################ + + # Used by TestHashing.test_hash and TestEquality.test_eq + eq_pairs = [(Test.Foo('test1'), Test.Foo('test1'))] + + # Used by TestEquality.test_ne + ne_pairs = [(Test.Foo('test1'), Test.Foo('runTest')), + (Test.Foo('test1'), Test.Bar('test1')), + (Test.Foo('test1'), Test.Bar('test2'))] + + ################################################################ + ### /Set up attributes used by inherited tests + + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + # ... + # "methodName defaults to "runTest"." + # + # Make sure it really is optional, and that it defaults to the proper + # thing. + def test_init__no_test_name(self): + class Test(unittest.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test().id()[-13:], '.Test.runTest') + + # test that TestCase can be instantiated with no args + # primarily for use at the interactive interpreter + test = unittest.TestCase() + test.assertEqual(3, 3) + with test.assertRaises(test.failureException): + test.assertEqual(3, 2) + + with self.assertRaises(AttributeError): + test.run() + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__valid(self): + class Test(unittest.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test('test').id()[-10:], '.Test.test') + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__invalid(self): + class Test(unittest.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + try: + Test('testfoo') + except ValueError: + pass + else: + self.fail("Failed to raise ValueError") + + # "Return the number of tests represented by the this test object. For + # TestCase instances, this will always be 1" + def test_countTestCases(self): + class Foo(unittest.TestCase): + def test(self): pass + + self.assertEqual(Foo('test').countTestCases(), 1) + + # "Return the default type of test result object to be used to run this + # test. For TestCase instances, this will always be + # unittest.TestResult; subclasses of TestCase should + # override this as necessary." + def test_defaultTestResult(self): + class Foo(unittest.TestCase): + def runTest(self): + pass + + result = Foo().defaultTestResult() + self.assertEqual(type(result), unittest.TestResult) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + self.assertEqual(events, expected) + + # "With a temporary result stopTestRun is called when setUp errors. + def test_run_call_order__error_in_setUp_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'addError', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "With a default result, an error in the test still results in stopTestRun + # being called." + def test_run_call_order__error_in_test_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', + 'tearDown', 'addError', 'stopTest', 'stopTestRun'] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addFailure', 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "When a test fails with a default result stopTestRun is still called." + def test_run_call_order__failure_in_test_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', + 'tearDown', 'addFailure', 'stopTest', 'stopTestRun'] + events = [] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + self.assertEqual(events, expected) + + # "When tearDown errors with a default result stopTestRun is still called." + def test_run_call_order__error_in_tearDown_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + events = [] + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "TestCase.run() still works when the defaultTestResult is a TestResult + # that does not support startTestRun and stopTestRun. + def test_run_call_order_default_result(self): + + class Foo(unittest.TestCase): + def defaultTestResult(self): + return ResultWithNoStartTestRunStopTestRun() + def test(self): + pass + + Foo('test').run() + + def _check_call_order__subtests(self, result, events, expected_events): + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + for i in [1, 2, 3]: + with self.subTest(i=i): + if i == 1: + self.fail('failure') + for j in [2, 3]: + with self.subTest(j=j): + if i * j == 6: + raise RuntimeError('raised by Foo.test') + 1 / 0 + + # Order is the following: + # i=1 => subtest failure + # i=2, j=2 => subtest success + # i=2, j=3 => subtest error + # i=3, j=2 => subtest error + # i=3, j=3 => subtest success + # toplevel => error + Foo(events).run(result) + self.assertEqual(events, expected_events) + + def test_run_call_order__subtests(self): + events = [] + result = LoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSubTestFailure', 'addSubTestSuccess', + 'addSubTestFailure', 'addSubTestFailure', + 'addSubTestSuccess', 'addError', 'stopTest'] + self._check_call_order__subtests(result, events, expected) + + def test_run_call_order__subtests_legacy(self): + # With a legacy result object (without an addSubTest method), + # text execution stops after the first subtest failure. + events = [] + result = LegacyLoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addFailure', 'stopTest'] + self._check_call_order__subtests(result, events, expected) + + def _check_call_order__subtests_success(self, result, events, expected_events): + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + for i in [1, 2]: + with self.subTest(i=i): + for j in [2, 3]: + with self.subTest(j=j): + pass + + Foo(events).run(result) + self.assertEqual(events, expected_events) + + def test_run_call_order__subtests_success(self): + events = [] + result = LoggingResult(events) + # The 6 subtest successes are individually recorded, in addition + # to the whole test success. + expected = (['startTest', 'setUp', 'test', 'tearDown'] + + 6 * ['addSubTestSuccess'] + + ['addSuccess', 'stopTest']) + self._check_call_order__subtests_success(result, events, expected) + + def test_run_call_order__subtests_success_legacy(self): + # With a legacy result, only the whole test success is recorded. + events = [] + result = LegacyLoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSuccess', 'stopTest'] + self._check_call_order__subtests_success(result, events, expected) + + def test_run_call_order__subtests_failfast(self): + events = [] + result = LoggingResult(events) + result.failfast = True + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + with self.subTest(i=1): + self.fail('failure') + with self.subTest(i=2): + self.fail('failure') + self.fail('failure') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSubTestFailure', 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + def test_subtests_failfast(self): + # Ensure proper test flow with subtests and failfast (issue #22894) + events = [] + + class Foo(unittest.TestCase): + def test_a(self): + with self.subTest(): + events.append('a1') + events.append('a2') + + def test_b(self): + with self.subTest(): + events.append('b1') + with self.subTest(): + self.fail('failure') + events.append('b2') + + def test_c(self): + events.append('c') + + result = unittest.TestResult() + result.failfast = True + suite = unittest.makeSuite(Foo) + suite.run(result) + + expected = ['a1', 'a2', 'b1'] + self.assertEqual(events, expected) + + def test_subtests_debug(self): + # Test debug() with a test that uses subTest() (bpo-34900) + events = [] + + class Foo(unittest.TestCase): + def test_a(self): + events.append('test case') + with self.subTest(): + events.append('subtest 1') + + Foo('test_a').debug() + + self.assertEqual(events, ['test case', 'subtest 1']) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework. The initial value of this + # attribute is AssertionError" + def test_failureException__default(self): + class Foo(unittest.TestCase): + def test(self): + pass + + self.assertIs(Foo('test').failureException, AssertionError) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__explicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest.TestCase): + def test(self): + raise RuntimeError() + + failureException = RuntimeError + + self.assertIs(Foo('test').failureException, RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__implicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest.TestCase): + def test(self): + self.fail("foo") + + failureException = RuntimeError + + self.assertIs(Foo('test').failureException, RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "The default implementation does nothing." + def test_setUp(self): + class Foo(unittest.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().setUp() + + # "The default implementation does nothing." + def test_tearDown(self): + class Foo(unittest.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().tearDown() + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + class Foo(unittest.TestCase): + def runTest(self): + pass + + self.assertIsInstance(Foo().id(), str) + + + # "If result is omitted or None, a temporary result object is created, + # used, and is made available to the caller. As TestCase owns the + # temporary result startTestRun and stopTestRun are called. + + def test_run__uses_defaultTestResult(self): + events = [] + defaultResult = LoggingResult(events) + + class Foo(unittest.TestCase): + def test(self): + events.append('test') + + def defaultTestResult(self): + return defaultResult + + # Make run() find a result object on its own + result = Foo('test').run() + + self.assertIs(result, defaultResult) + expected = ['startTestRun', 'startTest', 'test', 'addSuccess', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + + # "The result object is returned to run's caller" + def test_run__returns_given_result(self): + + class Foo(unittest.TestCase): + def test(self): + pass + + result = unittest.TestResult() + + retval = Foo('test').run(result) + self.assertIs(retval, result) + + + # "The same effect [as method run] may be had by simply calling the + # TestCase instance." + def test_call__invoking_an_instance_delegates_to_run(self): + resultIn = unittest.TestResult() + resultOut = unittest.TestResult() + + class Foo(unittest.TestCase): + def test(self): + pass + + def run(self, result): + self.assertIs(result, resultIn) + return resultOut + + retval = Foo('test')(resultIn) + + self.assertIs(retval, resultOut) + + + def testShortDescriptionWithoutDocstring(self): + self.assertIsNone(self.shortDescription()) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testShortDescriptionWithOneLineDocstring(self): + """Tests shortDescription() for a method with a docstring.""" + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a docstring.') + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testShortDescriptionWithMultiLineDocstring(self): + """Tests shortDescription() for a method with a longer docstring. + + This method ensures that only the first line of a docstring is + returned used in the short description, no matter how long the + whole thing is. + """ + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a longer ' + 'docstring.') + + def testShortDescriptionWhitespaceTrimming(self): + """ + Tests shortDescription() whitespace is trimmed, so that the first + line of nonwhite-space text becomes the docstring. + """ + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() whitespace is trimmed, so that the first') + + def testAddTypeEqualityFunc(self): + class SadSnake(object): + """Dummy class for test_addTypeEqualityFunc.""" + s1, s2 = SadSnake(), SadSnake() + self.assertFalse(s1 == s2) + def AllSnakesCreatedEqual(a, b, msg=None): + return type(a) == type(b) == SadSnake + self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) + self.assertEqual(s1, s2) + # No this doesn't clean up and remove the SadSnake equality func + # from this TestCase instance but since it's local nothing else + # will ever notice that. + + def testAssertIs(self): + thing = object() + self.assertIs(thing, thing) + self.assertRaises(self.failureException, self.assertIs, thing, object()) + + def testAssertIsNot(self): + thing = object() + self.assertIsNot(thing, object()) + self.assertRaises(self.failureException, self.assertIsNot, thing, thing) + + def testAssertIsInstance(self): + thing = [] + self.assertIsInstance(thing, list) + self.assertRaises(self.failureException, self.assertIsInstance, + thing, dict) + + def testAssertNotIsInstance(self): + thing = [] + self.assertNotIsInstance(thing, dict) + self.assertRaises(self.failureException, self.assertNotIsInstance, + thing, list) + + def testAssertIn(self): + animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} + + self.assertIn('a', 'abc') + self.assertIn(2, [1, 2, 3]) + self.assertIn('monkey', animals) + + self.assertNotIn('d', 'abc') + self.assertNotIn(0, [1, 2, 3]) + self.assertNotIn('otter', animals) + + self.assertRaises(self.failureException, self.assertIn, 'x', 'abc') + self.assertRaises(self.failureException, self.assertIn, 4, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertIn, 'elephant', + animals) + + self.assertRaises(self.failureException, self.assertNotIn, 'c', 'abc') + self.assertRaises(self.failureException, self.assertNotIn, 1, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertNotIn, 'cow', + animals) + + def testAssertDictContainsSubset(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + + self.assertDictContainsSubset({}, {}) + self.assertDictContainsSubset({}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2}) + self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({1: "one"}, {}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 2}, {'a': 1}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'c': 1}, {'a': 1}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) + + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) + + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing the failure msg + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'foo': one}, {'foo': '\uFFFD'}) + + def testAssertEqual(self): + equal_pairs = [ + ((), ()), + ({}, {}), + ([], []), + (set(), set()), + (frozenset(), frozenset())] + for a, b in equal_pairs: + # This mess of try excepts is to test the assertEqual behavior + # itself. + try: + self.assertEqual(a, b) + except self.failureException: + self.fail('assertEqual(%r, %r) failed' % (a, b)) + try: + self.assertEqual(a, b, msg='foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with msg= failed' % (a, b)) + try: + self.assertEqual(a, b, 'foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with third parameter failed' % + (a, b)) + + unequal_pairs = [ + ((), []), + ({}, set()), + (set([4,1]), frozenset([4,2])), + (frozenset([4,5]), set([2,3])), + (set([3,4]), set([5,4]))] + for a, b in unequal_pairs: + self.assertRaises(self.failureException, self.assertEqual, a, b) + self.assertRaises(self.failureException, self.assertEqual, a, b, + 'foo') + self.assertRaises(self.failureException, self.assertEqual, a, b, + msg='foo') + + def testEquality(self): + self.assertListEqual([], []) + self.assertTupleEqual((), ()) + self.assertSequenceEqual([], ()) + + a = [0, 'a', []] + b = [] + self.assertRaises(unittest.TestCase.failureException, + self.assertListEqual, a, b) + self.assertRaises(unittest.TestCase.failureException, + self.assertListEqual, tuple(a), tuple(b)) + self.assertRaises(unittest.TestCase.failureException, + self.assertSequenceEqual, a, tuple(b)) + + b.extend(a) + self.assertListEqual(a, b) + self.assertTupleEqual(tuple(a), tuple(b)) + self.assertSequenceEqual(a, tuple(b)) + self.assertSequenceEqual(tuple(a), b) + + self.assertRaises(self.failureException, self.assertListEqual, + a, tuple(b)) + self.assertRaises(self.failureException, self.assertTupleEqual, + tuple(a), b) + self.assertRaises(self.failureException, self.assertListEqual, None, b) + self.assertRaises(self.failureException, self.assertTupleEqual, None, + tuple(b)) + self.assertRaises(self.failureException, self.assertSequenceEqual, + None, tuple(b)) + self.assertRaises(self.failureException, self.assertListEqual, 1, 1) + self.assertRaises(self.failureException, self.assertTupleEqual, 1, 1) + self.assertRaises(self.failureException, self.assertSequenceEqual, + 1, 1) + + self.assertDictEqual({}, {}) + + c = { 'x': 1 } + d = {} + self.assertRaises(unittest.TestCase.failureException, + self.assertDictEqual, c, d) + + d.update(c) + self.assertDictEqual(c, d) + + d['x'] = 0 + self.assertRaises(unittest.TestCase.failureException, + self.assertDictEqual, c, d, 'These are unequal') + + self.assertRaises(self.failureException, self.assertDictEqual, None, d) + self.assertRaises(self.failureException, self.assertDictEqual, [], d) + self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) + + def testAssertSequenceEqualMaxDiff(self): + self.assertEqual(self.maxDiff, 80*8) + seq1 = 'a' + 'x' * 80**2 + seq2 = 'b' + 'x' * 80**2 + diff = '\n'.join(difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + # the +1 is the leading \n added by assertSequenceEqual + omitted = unittest.case.DIFF_OMITTED % (len(diff) + 1,) + + self.maxDiff = len(diff)//2 + try: + + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertLess(len(msg), len(diff)) + self.assertIn(omitted, msg) + + self.maxDiff = len(diff) * 2 + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertGreater(len(msg), len(diff)) + self.assertNotIn(omitted, msg) + + self.maxDiff = None + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertGreater(len(msg), len(diff)) + self.assertNotIn(omitted, msg) + + def testTruncateMessage(self): + self.maxDiff = 1 + message = self._truncateMessage('foo', 'bar') + omitted = unittest.case.DIFF_OMITTED % len('bar') + self.assertEqual(message, 'foo' + omitted) + + self.maxDiff = None + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + self.maxDiff = 4 + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + def testAssertDictEqualTruncates(self): + test = unittest.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertDictEqual({}, {1: 0}) + except self.failureException as e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertDictEqual did not fail') + + def testAssertMultiLineEqualTruncates(self): + test = unittest.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertMultiLineEqual('foo', 'bar') + except self.failureException as e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertMultiLineEqual did not fail') + + def testAssertEqual_diffThreshold(self): + # check threshold value + self.assertEqual(self._diffThreshold, 2**16) + # disable madDiff to get diff markers + self.maxDiff = None + + # set a lower threshold value and add a cleanup to restore it + old_threshold = self._diffThreshold + self._diffThreshold = 2**5 + self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) + + # under the threshold: diff marker (^) in error message + s = 'x' * (2**4) + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s + 'a', s + 'b') + self.assertIn('^', str(cm.exception)) + self.assertEqual(s + 'a', s + 'a') + + # over the threshold: diff not used and marker (^) not in error message + s = 'x' * (2**6) + # if the path that uses difflib is taken, _truncateMessage will be + # called -- replace it with explodingTruncation to verify that this + # doesn't happen + def explodingTruncation(message, diff): + raise SystemError('this should not be raised') + old_truncate = self._truncateMessage + self._truncateMessage = explodingTruncation + self.addCleanup(lambda: setattr(self, '_truncateMessage', old_truncate)) + + s1, s2 = s + 'a', s + 'b' + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + self.assertNotIn('^', str(cm.exception)) + self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2)) + self.assertEqual(s + 'a', s + 'a') + + def testAssertEqual_shorten(self): + # set a lower threshold value and add a cleanup to restore it + old_threshold = self._diffThreshold + self._diffThreshold = 0 + self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) + + s = 'x' * 100 + s1, s2 = s + 'a', s + 'b' + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[35 chars]' + 'x' * 61 + self.assertEqual(str(cm.exception), "'%sa' != '%sb'" % (c, c)) + self.assertEqual(s + 'a', s + 'a') + + p = 'y' * 50 + s1, s2 = s + 'a' + p, s + 'b' + p + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[85 chars]xxxxxxxxxxx' + self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, p, c, p)) + + p = 'y' * 100 + s1, s2 = s + 'a' + p, s + 'b' + p + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[91 chars]xxxxx' + d = 'y' * 40 + '[56 chars]yyyy' + self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, d, c, d)) + + def testAssertCountEqual(self): + a = object() + self.assertCountEqual([1, 2, 3], [3, 2, 1]) + self.assertCountEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) + self.assertCountEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2)) + self.assertCountEqual([1, "2", "a", "a"], ["a", "2", True, "a"]) + self.assertRaises(self.failureException, self.assertCountEqual, + [1, 2] + [3] * 100, [1] * 100 + [2, 3]) + self.assertRaises(self.failureException, self.assertCountEqual, + [1, "2", "a", "a"], ["a", "2", True, 1]) + self.assertRaises(self.failureException, self.assertCountEqual, + [10], [10, 11]) + self.assertRaises(self.failureException, self.assertCountEqual, + [10, 11], [10]) + self.assertRaises(self.failureException, self.assertCountEqual, + [10, 11, 10], [10, 11]) + + # Test that sequences of unhashable objects can be tested for sameness: + self.assertCountEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]]) + # Test that iterator of unhashable objects can be tested for sameness: + self.assertCountEqual(iter([1, 2, [], 3, 4]), + iter([1, 2, [], 3, 4])) + + # hashable types, but not orderable + self.assertRaises(self.failureException, self.assertCountEqual, + [], [divmod, 'x', 1, 5j, 2j, frozenset()]) + # comparing dicts + self.assertCountEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + # comparing heterogeneous non-hashable sequences + self.assertCountEqual([1, 'x', divmod, []], [divmod, [], 'x', 1]) + self.assertRaises(self.failureException, self.assertCountEqual, + [], [divmod, [], 'x', 1, 5j, 2j, set()]) + self.assertRaises(self.failureException, self.assertCountEqual, + [[1]], [[2]]) + + # Same elements, but not same sequence length + self.assertRaises(self.failureException, self.assertCountEqual, + [1, 1, 2], [2, 1]) + self.assertRaises(self.failureException, self.assertCountEqual, + [1, 1, "2", "a", "a"], ["2", "2", True, "a"]) + self.assertRaises(self.failureException, self.assertCountEqual, + [1, {'b': 2}, None, True], [{'b': 2}, True, None]) + + # Same elements which don't reliably compare, in + # different order, see issue 10242 + a = [{2,4}, {1,2}] + b = a[::-1] + self.assertCountEqual(a, b) + + # test utility functions supporting assertCountEqual() + + diffs = set(unittest.util._count_diff_all_purpose('aaabccd', 'abbbcce')) + expected = {(3,1,'a'), (1,3,'b'), (1,0,'d'), (0,1,'e')} + self.assertEqual(diffs, expected) + + diffs = unittest.util._count_diff_all_purpose([[]], []) + self.assertEqual(diffs, [(1, 0, [])]) + + diffs = set(unittest.util._count_diff_hashable('aaabccd', 'abbbcce')) + expected = {(3,1,'a'), (1,3,'b'), (1,0,'d'), (0,1,'e')} + self.assertEqual(diffs, expected) + + def testAssertSetEqual(self): + set1 = set() + set2 = set() + self.assertSetEqual(set1, set2) + + self.assertRaises(self.failureException, self.assertSetEqual, None, set2) + self.assertRaises(self.failureException, self.assertSetEqual, [], set2) + self.assertRaises(self.failureException, self.assertSetEqual, set1, None) + self.assertRaises(self.failureException, self.assertSetEqual, set1, []) + + set1 = set(['a']) + set2 = set() + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = set(['a']) + self.assertSetEqual(set1, set2) + + set1 = set(['a']) + set2 = set(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = frozenset(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a', 'b']) + set2 = frozenset(['a', 'b']) + self.assertSetEqual(set1, set2) + + set1 = set() + set2 = "foo" + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + self.assertRaises(self.failureException, self.assertSetEqual, set2, set1) + + # make sure any string formatting is tuple-safe + set1 = set([(0, 1), (2, 3)]) + set2 = set([(4, 5)]) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + def testInequality(self): + # Try ints + self.assertGreater(2, 1) + self.assertGreaterEqual(2, 1) + self.assertGreaterEqual(1, 1) + self.assertLess(1, 2) + self.assertLessEqual(1, 2) + self.assertLessEqual(1, 1) + self.assertRaises(self.failureException, self.assertGreater, 1, 2) + self.assertRaises(self.failureException, self.assertGreater, 1, 1) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1, 2) + self.assertRaises(self.failureException, self.assertLess, 2, 1) + self.assertRaises(self.failureException, self.assertLess, 1, 1) + self.assertRaises(self.failureException, self.assertLessEqual, 2, 1) + + # Try Floats + self.assertGreater(1.1, 1.0) + self.assertGreaterEqual(1.1, 1.0) + self.assertGreaterEqual(1.0, 1.0) + self.assertLess(1.0, 1.1) + self.assertLessEqual(1.0, 1.1) + self.assertLessEqual(1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertLess, 1.1, 1.0) + self.assertRaises(self.failureException, self.assertLess, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertLessEqual, 1.1, 1.0) + + # Try Strings + self.assertGreater('bug', 'ant') + self.assertGreaterEqual('bug', 'ant') + self.assertGreaterEqual('ant', 'ant') + self.assertLess('ant', 'bug') + self.assertLessEqual('ant', 'bug') + self.assertLessEqual('ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', 'ant') + + # Try bytes + self.assertGreater(b'bug', b'ant') + self.assertGreaterEqual(b'bug', b'ant') + self.assertGreaterEqual(b'ant', b'ant') + self.assertLess(b'ant', b'bug') + self.assertLessEqual(b'ant', b'bug') + self.assertLessEqual(b'ant', b'ant') + self.assertRaises(self.failureException, self.assertGreater, b'ant', b'bug') + self.assertRaises(self.failureException, self.assertGreater, b'ant', b'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, b'ant', + b'bug') + self.assertRaises(self.failureException, self.assertLess, b'bug', b'ant') + self.assertRaises(self.failureException, self.assertLess, b'ant', b'ant') + self.assertRaises(self.failureException, self.assertLessEqual, b'bug', b'ant') + + def testAssertMultiLineEqual(self): + sample_text = """\ +http://www.python.org/doc/2.3/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] +""" + revised_sample_text = """\ +http://www.python.org/doc/2.4.1/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] You may provide your + own implementation that does not subclass from TestCase, of course. +""" + sample_text_error = """\ +- http://www.python.org/doc/2.3/lib/module-unittest.html +? ^ ++ http://www.python.org/doc/2.4.1/lib/module-unittest.html +? ^^^ + test case +- A test case is the smallest unit of testing. [...] ++ A test case is the smallest unit of testing. [...] You may provide your +? +++++++++++++++++++++ ++ own implementation that does not subclass from TestCase, of course. +""" + self.maxDiff = None + try: + self.assertMultiLineEqual(sample_text, revised_sample_text) + except self.failureException as e: + # need to remove the first line of the error message + error = str(e).split('\n', 1)[1] + self.assertEqual(sample_text_error, error) + + def testAssertEqualSingleLine(self): + sample_text = "laden swallows fly slowly" + revised_sample_text = "unladen swallows fly quickly" + sample_text_error = """\ +- laden swallows fly slowly +? ^^^^ ++ unladen swallows fly quickly +? ++ ^^^^^ +""" + try: + self.assertEqual(sample_text, revised_sample_text) + except self.failureException as e: + # need to remove the first line of the error message + error = str(e).split('\n', 1)[1] + self.assertEqual(sample_text_error, error) + + def testEqualityBytesWarning(self): + if sys.flags.bytes_warning: + def bytes_warning(): + return self.assertWarnsRegex(BytesWarning, + 'Comparison between bytes and string') + else: + def bytes_warning(): + return contextlib.ExitStack() + + with bytes_warning(), self.assertRaises(self.failureException): + self.assertEqual('a', b'a') + with bytes_warning(): + self.assertNotEqual('a', b'a') + + a = [0, 'a'] + b = [0, b'a'] + with bytes_warning(), self.assertRaises(self.failureException): + self.assertListEqual(a, b) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertTupleEqual(tuple(a), tuple(b)) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertSequenceEqual(a, tuple(b)) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertSequenceEqual(tuple(a), b) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertSequenceEqual('a', b'a') + with bytes_warning(), self.assertRaises(self.failureException): + self.assertSetEqual(set(a), set(b)) + + with self.assertRaises(self.failureException): + self.assertListEqual(a, tuple(b)) + with self.assertRaises(self.failureException): + self.assertTupleEqual(tuple(a), b) + + a = [0, b'a'] + b = [0] + with self.assertRaises(self.failureException): + self.assertListEqual(a, b) + with self.assertRaises(self.failureException): + self.assertTupleEqual(tuple(a), tuple(b)) + with self.assertRaises(self.failureException): + self.assertSequenceEqual(a, tuple(b)) + with self.assertRaises(self.failureException): + self.assertSequenceEqual(tuple(a), b) + with self.assertRaises(self.failureException): + self.assertSetEqual(set(a), set(b)) + + a = [0] + b = [0, b'a'] + with self.assertRaises(self.failureException): + self.assertListEqual(a, b) + with self.assertRaises(self.failureException): + self.assertTupleEqual(tuple(a), tuple(b)) + with self.assertRaises(self.failureException): + self.assertSequenceEqual(a, tuple(b)) + with self.assertRaises(self.failureException): + self.assertSequenceEqual(tuple(a), b) + with self.assertRaises(self.failureException): + self.assertSetEqual(set(a), set(b)) + + with bytes_warning(), self.assertRaises(self.failureException): + self.assertDictEqual({'a': 0}, {b'a': 0}) + with self.assertRaises(self.failureException): + self.assertDictEqual({}, {b'a': 0}) + with self.assertRaises(self.failureException): + self.assertDictEqual({b'a': 0}, {}) + + with self.assertRaises(self.failureException): + self.assertCountEqual([b'a', b'a'], [b'a', b'a', b'a']) + with bytes_warning(): + self.assertCountEqual(['a', b'a'], ['a', b'a']) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertCountEqual(['a', 'a'], [b'a', b'a']) + with bytes_warning(), self.assertRaises(self.failureException): + self.assertCountEqual(['a', 'a', []], [b'a', b'a', []]) + + def testAssertIsNone(self): + self.assertIsNone(None) + self.assertRaises(self.failureException, self.assertIsNone, False) + self.assertIsNotNone('DjZoPloGears on Rails') + self.assertRaises(self.failureException, self.assertIsNotNone, None) + + def testAssertRegex(self): + self.assertRegex('asdfabasdf', r'ab+') + self.assertRaises(self.failureException, self.assertRegex, + 'saaas', r'aaaa') + + def testAssertRaisesCallable(self): + class ExceptionMock(Exception): + pass + def Stub(): + raise ExceptionMock('We expect') + self.assertRaises(ExceptionMock, Stub) + # A tuple of exception classes is accepted + self.assertRaises((ValueError, ExceptionMock), Stub) + # *args and **kwargs also work + self.assertRaises(ValueError, int, '19', base=8) + # Failure when no exception is raised + with self.assertRaises(self.failureException): + self.assertRaises(ExceptionMock, lambda: 0) + # Failure when the function is None + with self.assertRaises(TypeError): + self.assertRaises(ExceptionMock, None) + # Failure when another exception is raised + with self.assertRaises(ExceptionMock): + self.assertRaises(ValueError, Stub) + + def testAssertRaisesContext(self): + class ExceptionMock(Exception): + pass + def Stub(): + raise ExceptionMock('We expect') + with self.assertRaises(ExceptionMock): + Stub() + # A tuple of exception classes is accepted + with self.assertRaises((ValueError, ExceptionMock)) as cm: + Stub() + # The context manager exposes caught exception + self.assertIsInstance(cm.exception, ExceptionMock) + self.assertEqual(cm.exception.args[0], 'We expect') + # *args and **kwargs also work + with self.assertRaises(ValueError): + int('19', base=8) + # Failure when no exception is raised + with self.assertRaises(self.failureException): + with self.assertRaises(ExceptionMock): + pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertRaises(ExceptionMock, msg='foobar'): + pass + # Invalid keyword argument + with self.assertRaisesRegex(TypeError, 'foobar'): + with self.assertRaises(ExceptionMock, foobar=42): + pass + # Failure when another exception is raised + with self.assertRaises(ExceptionMock): + self.assertRaises(ValueError, Stub) + + def testAssertRaisesNoExceptionType(self): + with self.assertRaises(TypeError): + self.assertRaises() + with self.assertRaises(TypeError): + self.assertRaises(1) + with self.assertRaises(TypeError): + self.assertRaises(object) + with self.assertRaises(TypeError): + self.assertRaises((ValueError, 1)) + with self.assertRaises(TypeError): + self.assertRaises((ValueError, object)) + + def testAssertRaisesRefcount(self): + # bpo-23890: assertRaises() must not keep objects alive longer + # than expected + def func() : + try: + raise ValueError + except ValueError: + raise ValueError + + refcount = sys.getrefcount(func) + self.assertRaises(ValueError, func) + self.assertEqual(refcount, sys.getrefcount(func)) + + def testAssertRaisesRegex(self): + class ExceptionMock(Exception): + pass + + def Stub(): + raise ExceptionMock('We expect') + + self.assertRaisesRegex(ExceptionMock, re.compile('expect$'), Stub) + self.assertRaisesRegex(ExceptionMock, 'expect$', Stub) + with self.assertRaises(TypeError): + self.assertRaisesRegex(ExceptionMock, 'expect$', None) + + def testAssertNotRaisesRegex(self): + self.assertRaisesRegex( + self.failureException, '^Exception not raised by $', + self.assertRaisesRegex, Exception, re.compile('x'), + lambda: None) + self.assertRaisesRegex( + self.failureException, '^Exception not raised by $', + self.assertRaisesRegex, Exception, 'x', + lambda: None) + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertRaisesRegex(Exception, 'expect', msg='foobar'): + pass + # Invalid keyword argument + with self.assertRaisesRegex(TypeError, 'foobar'): + with self.assertRaisesRegex(Exception, 'expect', foobar=42): + pass + + def testAssertRaisesRegexInvalidRegex(self): + # Issue 20145. + class MyExc(Exception): + pass + self.assertRaises(TypeError, self.assertRaisesRegex, MyExc, lambda: True) + + def testAssertWarnsRegexInvalidRegex(self): + # Issue 20145. + class MyWarn(Warning): + pass + self.assertRaises(TypeError, self.assertWarnsRegex, MyWarn, lambda: True) + + def testAssertRaisesRegexMismatch(self): + def Stub(): + raise Exception('Unexpected') + + self.assertRaisesRegex( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegex, Exception, '^Expected$', + Stub) + self.assertRaisesRegex( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegex, Exception, + re.compile('^Expected$'), Stub) + + def testAssertRaisesExcValue(self): + class ExceptionMock(Exception): + pass + + def Stub(foo): + raise ExceptionMock(foo) + v = "particular value" + + ctx = self.assertRaises(ExceptionMock) + with ctx: + Stub(v) + e = ctx.exception + self.assertIsInstance(e, ExceptionMock) + self.assertEqual(e.args[0], v) + + def testAssertRaisesRegexNoExceptionType(self): + with self.assertRaises(TypeError): + self.assertRaisesRegex() + with self.assertRaises(TypeError): + self.assertRaisesRegex(ValueError) + with self.assertRaises(TypeError): + self.assertRaisesRegex(1, 'expect') + with self.assertRaises(TypeError): + self.assertRaisesRegex(object, 'expect') + with self.assertRaises(TypeError): + self.assertRaisesRegex((ValueError, 1), 'expect') + with self.assertRaises(TypeError): + self.assertRaisesRegex((ValueError, object), 'expect') + + def testAssertWarnsCallable(self): + def _runtime_warn(): + warnings.warn("foo", RuntimeWarning) + # Success when the right warning is triggered, even several times + self.assertWarns(RuntimeWarning, _runtime_warn) + self.assertWarns(RuntimeWarning, _runtime_warn) + # A tuple of warning classes is accepted + self.assertWarns((DeprecationWarning, RuntimeWarning), _runtime_warn) + # *args and **kwargs also work + self.assertWarns(RuntimeWarning, + warnings.warn, "foo", category=RuntimeWarning) + # Failure when no warning is triggered + with self.assertRaises(self.failureException): + self.assertWarns(RuntimeWarning, lambda: 0) + # Failure when the function is None + with self.assertRaises(TypeError): + self.assertWarns(RuntimeWarning, None) + # Failure when another warning is triggered + with warnings.catch_warnings(): + # Force default filter (in case tests are run with -We) + warnings.simplefilter("default", RuntimeWarning) + with self.assertRaises(self.failureException): + self.assertWarns(DeprecationWarning, _runtime_warn) + # Filters for other warnings are not modified + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaises(RuntimeWarning): + self.assertWarns(DeprecationWarning, _runtime_warn) + + def testAssertWarnsContext(self): + # Believe it or not, it is preferable to duplicate all tests above, + # to make sure the __warningregistry__ $@ is circumvented correctly. + def _runtime_warn(): + warnings.warn("foo", RuntimeWarning) + _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1] + with self.assertWarns(RuntimeWarning) as cm: + _runtime_warn() + # A tuple of warning classes is accepted + with self.assertWarns((DeprecationWarning, RuntimeWarning)) as cm: + _runtime_warn() + # The context manager exposes various useful attributes + self.assertIsInstance(cm.warning, RuntimeWarning) + self.assertEqual(cm.warning.args[0], "foo") + self.assertIn("test_case.py", cm.filename) + self.assertEqual(cm.lineno, _runtime_warn_lineno + 1) + # Same with several warnings + with self.assertWarns(RuntimeWarning): + _runtime_warn() + _runtime_warn() + with self.assertWarns(RuntimeWarning): + warnings.warn("foo", category=RuntimeWarning) + # Failure when no warning is triggered + with self.assertRaises(self.failureException): + with self.assertWarns(RuntimeWarning): + pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertWarns(RuntimeWarning, msg='foobar'): + pass + # Invalid keyword argument + with self.assertRaisesRegex(TypeError, 'foobar'): + with self.assertWarns(RuntimeWarning, foobar=42): + pass + # Failure when another warning is triggered + with warnings.catch_warnings(): + # Force default filter (in case tests are run with -We) + warnings.simplefilter("default", RuntimeWarning) + with self.assertRaises(self.failureException): + with self.assertWarns(DeprecationWarning): + _runtime_warn() + # Filters for other warnings are not modified + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaises(RuntimeWarning): + with self.assertWarns(DeprecationWarning): + _runtime_warn() + + def testAssertWarnsNoExceptionType(self): + with self.assertRaises(TypeError): + self.assertWarns() + with self.assertRaises(TypeError): + self.assertWarns(1) + with self.assertRaises(TypeError): + self.assertWarns(object) + with self.assertRaises(TypeError): + self.assertWarns((UserWarning, 1)) + with self.assertRaises(TypeError): + self.assertWarns((UserWarning, object)) + with self.assertRaises(TypeError): + self.assertWarns((UserWarning, Exception)) + + def testAssertWarnsRegexCallable(self): + def _runtime_warn(msg): + warnings.warn(msg, RuntimeWarning) + self.assertWarnsRegex(RuntimeWarning, "o+", + _runtime_warn, "foox") + # Failure when no warning is triggered + with self.assertRaises(self.failureException): + self.assertWarnsRegex(RuntimeWarning, "o+", + lambda: 0) + # Failure when the function is None + with self.assertRaises(TypeError): + self.assertWarnsRegex(RuntimeWarning, "o+", None) + # Failure when another warning is triggered + with warnings.catch_warnings(): + # Force default filter (in case tests are run with -We) + warnings.simplefilter("default", RuntimeWarning) + with self.assertRaises(self.failureException): + self.assertWarnsRegex(DeprecationWarning, "o+", + _runtime_warn, "foox") + # Failure when message doesn't match + with self.assertRaises(self.failureException): + self.assertWarnsRegex(RuntimeWarning, "o+", + _runtime_warn, "barz") + # A little trickier: we ask RuntimeWarnings to be raised, and then + # check for some of them. It is implementation-defined whether + # non-matching RuntimeWarnings are simply re-raised, or produce a + # failureException. + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaises((RuntimeWarning, self.failureException)): + self.assertWarnsRegex(RuntimeWarning, "o+", + _runtime_warn, "barz") + + def testAssertWarnsRegexContext(self): + # Same as above, but with assertWarnsRegex as a context manager + def _runtime_warn(msg): + warnings.warn(msg, RuntimeWarning) + _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1] + with self.assertWarnsRegex(RuntimeWarning, "o+") as cm: + _runtime_warn("foox") + self.assertIsInstance(cm.warning, RuntimeWarning) + self.assertEqual(cm.warning.args[0], "foox") + self.assertIn("test_case.py", cm.filename) + self.assertEqual(cm.lineno, _runtime_warn_lineno + 1) + # Failure when no warning is triggered + with self.assertRaises(self.failureException): + with self.assertWarnsRegex(RuntimeWarning, "o+"): + pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertWarnsRegex(RuntimeWarning, 'o+', msg='foobar'): + pass + # Invalid keyword argument + with self.assertRaisesRegex(TypeError, 'foobar'): + with self.assertWarnsRegex(RuntimeWarning, 'o+', foobar=42): + pass + # Failure when another warning is triggered + with warnings.catch_warnings(): + # Force default filter (in case tests are run with -We) + warnings.simplefilter("default", RuntimeWarning) + with self.assertRaises(self.failureException): + with self.assertWarnsRegex(DeprecationWarning, "o+"): + _runtime_warn("foox") + # Failure when message doesn't match + with self.assertRaises(self.failureException): + with self.assertWarnsRegex(RuntimeWarning, "o+"): + _runtime_warn("barz") + # A little trickier: we ask RuntimeWarnings to be raised, and then + # check for some of them. It is implementation-defined whether + # non-matching RuntimeWarnings are simply re-raised, or produce a + # failureException. + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaises((RuntimeWarning, self.failureException)): + with self.assertWarnsRegex(RuntimeWarning, "o+"): + _runtime_warn("barz") + + def testAssertWarnsRegexNoExceptionType(self): + with self.assertRaises(TypeError): + self.assertWarnsRegex() + with self.assertRaises(TypeError): + self.assertWarnsRegex(UserWarning) + with self.assertRaises(TypeError): + self.assertWarnsRegex(1, 'expect') + with self.assertRaises(TypeError): + self.assertWarnsRegex(object, 'expect') + with self.assertRaises(TypeError): + self.assertWarnsRegex((UserWarning, 1), 'expect') + with self.assertRaises(TypeError): + self.assertWarnsRegex((UserWarning, object), 'expect') + with self.assertRaises(TypeError): + self.assertWarnsRegex((UserWarning, Exception), 'expect') + + @contextlib.contextmanager + def assertNoStderr(self): + with captured_stderr() as buf: + yield + self.assertEqual(buf.getvalue(), "") + + def assertLogRecords(self, records, matches): + self.assertEqual(len(records), len(matches)) + for rec, match in zip(records, matches): + self.assertIsInstance(rec, logging.LogRecord) + for k, v in match.items(): + self.assertEqual(getattr(rec, k), v) + + def testAssertLogsDefaults(self): + # defaults: root logger, level INFO + with self.assertNoStderr(): + with self.assertLogs() as cm: + log_foo.info("1") + log_foobar.debug("2") + self.assertEqual(cm.output, ["INFO:foo:1"]) + self.assertLogRecords(cm.records, [{'name': 'foo'}]) + + def testAssertLogsTwoMatchingMessages(self): + # Same, but with two matching log messages + with self.assertNoStderr(): + with self.assertLogs() as cm: + log_foo.info("1") + log_foobar.debug("2") + log_quux.warning("3") + self.assertEqual(cm.output, ["INFO:foo:1", "WARNING:quux:3"]) + self.assertLogRecords(cm.records, + [{'name': 'foo'}, {'name': 'quux'}]) + + def checkAssertLogsPerLevel(self, level): + # Check level filtering + with self.assertNoStderr(): + with self.assertLogs(level=level) as cm: + log_foo.warning("1") + log_foobar.error("2") + log_quux.critical("3") + self.assertEqual(cm.output, ["ERROR:foo.bar:2", "CRITICAL:quux:3"]) + self.assertLogRecords(cm.records, + [{'name': 'foo.bar'}, {'name': 'quux'}]) + + def testAssertLogsPerLevel(self): + self.checkAssertLogsPerLevel(logging.ERROR) + self.checkAssertLogsPerLevel('ERROR') + + def checkAssertLogsPerLogger(self, logger): + # Check per-logger filtering + with self.assertNoStderr(): + with self.assertLogs(level='DEBUG') as outer_cm: + with self.assertLogs(logger, level='DEBUG') as cm: + log_foo.info("1") + log_foobar.debug("2") + log_quux.warning("3") + self.assertEqual(cm.output, ["INFO:foo:1", "DEBUG:foo.bar:2"]) + self.assertLogRecords(cm.records, + [{'name': 'foo'}, {'name': 'foo.bar'}]) + # The outer catchall caught the quux log + self.assertEqual(outer_cm.output, ["WARNING:quux:3"]) + + def testAssertLogsPerLogger(self): + self.checkAssertLogsPerLogger(logging.getLogger('foo')) + self.checkAssertLogsPerLogger('foo') + + def testAssertLogsFailureNoLogs(self): + # Failure due to no logs + with self.assertNoStderr(): + with self.assertRaises(self.failureException): + with self.assertLogs(): + pass + + def testAssertLogsFailureLevelTooHigh(self): + # Failure due to level too high + with self.assertNoStderr(): + with self.assertRaises(self.failureException): + with self.assertLogs(level='WARNING'): + log_foo.info("1") + + def testAssertLogsFailureMismatchingLogger(self): + # Failure due to mismatching logger (and the logged message is + # passed through) + with self.assertLogs('quux', level='ERROR'): + with self.assertRaises(self.failureException): + with self.assertLogs('foo'): + log_quux.error("1") + + def testDeprecatedMethodNames(self): + """ + Test that the deprecated methods raise a DeprecationWarning. See #9424. + """ + old = ( + (self.failIfEqual, (3, 5)), + (self.assertNotEquals, (3, 5)), + (self.failUnlessEqual, (3, 3)), + (self.assertEquals, (3, 3)), + (self.failUnlessAlmostEqual, (2.0, 2.0)), + (self.assertAlmostEquals, (2.0, 2.0)), + (self.failIfAlmostEqual, (3.0, 5.0)), + (self.assertNotAlmostEquals, (3.0, 5.0)), + (self.failUnless, (True,)), + (self.assert_, (True,)), + (self.failUnlessRaises, (TypeError, lambda _: 3.14 + 'spam')), + (self.failIf, (False,)), + (self.assertDictContainsSubset, (dict(a=1, b=2), dict(a=1, b=2, c=3))), + (self.assertRaisesRegexp, (KeyError, 'foo', lambda: {}['foo'])), + (self.assertRegexpMatches, ('bar', 'bar')), + ) + for meth, args in old: + with self.assertWarns(DeprecationWarning): + meth(*args) + + # disable this test for now. When the version where the fail* methods will + # be removed is decided, re-enable it and update the version + def _testDeprecatedFailMethods(self): + """Test that the deprecated fail* methods get removed in 3.x""" + if sys.version_info[:2] < (3, 3): + return + deprecated_names = [ + 'failIfEqual', 'failUnlessEqual', 'failUnlessAlmostEqual', + 'failIfAlmostEqual', 'failUnless', 'failUnlessRaises', 'failIf', + 'assertDictContainsSubset', + ] + for deprecated_name in deprecated_names: + with self.assertRaises(AttributeError): + getattr(self, deprecated_name) # remove these in 3.x + + def testDeepcopy(self): + # Issue: 5660 + class TestableTest(unittest.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + + # This shouldn't blow up + deepcopy(test) + + def testPickle(self): + # Issue 10326 + + # Can't use TestCase classes defined in Test class as + # pickle does not work with inner classes + test = unittest.TestCase('run') + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + + # blew up prior to fix + pickled_test = pickle.dumps(test, protocol=protocol) + unpickled_test = pickle.loads(pickled_test) + self.assertEqual(test, unpickled_test) + + # exercise the TestCase instance in a way that will invoke + # the type equality lookup mechanism + unpickled_test.assertEqual(set(), set()) + + def testKeyboardInterrupt(self): + def _raise(self=None): + raise KeyboardInterrupt + def nothing(self): + pass + + class Test1(unittest.TestCase): + test_something = _raise + + class Test2(unittest.TestCase): + setUp = _raise + test_something = nothing + + class Test3(unittest.TestCase): + test_something = nothing + tearDown = _raise + + class Test4(unittest.TestCase): + def test_something(self): + self.addCleanup(_raise) + + for klass in (Test1, Test2, Test3, Test4): + with self.assertRaises(KeyboardInterrupt): + klass('test_something').run() + + def testSkippingEverywhere(self): + def _skip(self=None): + raise unittest.SkipTest('some reason') + def nothing(self): + pass + + class Test1(unittest.TestCase): + test_something = _skip + + class Test2(unittest.TestCase): + setUp = _skip + test_something = nothing + + class Test3(unittest.TestCase): + test_something = nothing + tearDown = _skip + + class Test4(unittest.TestCase): + def test_something(self): + self.addCleanup(_skip) + + for klass in (Test1, Test2, Test3, Test4): + result = unittest.TestResult() + klass('test_something').run(result) + self.assertEqual(len(result.skipped), 1) + self.assertEqual(result.testsRun, 1) + + def testSystemExit(self): + def _raise(self=None): + raise SystemExit + def nothing(self): + pass + + class Test1(unittest.TestCase): + test_something = _raise + + class Test2(unittest.TestCase): + setUp = _raise + test_something = nothing + + class Test3(unittest.TestCase): + test_something = nothing + tearDown = _raise + + class Test4(unittest.TestCase): + def test_something(self): + self.addCleanup(_raise) + + for klass in (Test1, Test2, Test3, Test4): + result = unittest.TestResult() + klass('test_something').run(result) + self.assertEqual(len(result.errors), 1) + self.assertEqual(result.testsRun, 1) + + @support.cpython_only + def testNoCycles(self): + case = unittest.TestCase() + wr = weakref.ref(case) + with support.disable_gc(): + del case + self.assertFalse(wr()) + + def test_no_exception_leak(self): + # Issue #19880: TestCase.run() should not keep a reference + # to the exception + class MyException(Exception): + ninstance = 0 + + def __init__(self): + MyException.ninstance += 1 + Exception.__init__(self) + + def __del__(self): + MyException.ninstance -= 1 + + class TestCase(unittest.TestCase): + def test1(self): + raise MyException() + + @unittest.expectedFailure + def test2(self): + raise MyException() + + for method_name in ('test1', 'test2'): + testcase = TestCase(method_name) + testcase.run() + self.assertEqual(MyException.ninstance, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_discovery.py b/Monika After Story/game/python-packages/unittest/test/test_discovery.py new file mode 100644 index 0000000000..16e081e1fb --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_discovery.py @@ -0,0 +1,879 @@ +import os.path +from os.path import abspath +import re +import sys +import types +import pickle +from test import support +import test.test_importlib.util + +import unittest +import unittest.mock +import unittest.test + + +class TestableTestProgram(unittest.TestProgram): + module = None + exit = True + defaultTest = failfast = catchbreak = buffer = None + verbosity = 1 + progName = '' + testRunner = testLoader = None + + def __init__(self): + pass + + +class TestDiscovery(unittest.TestCase): + + # Heavily mocked tests so I can avoid hitting the filesystem + def test_get_name_from_path(self): + loader = unittest.TestLoader() + loader._top_level_dir = '/foo' + name = loader._get_name_from_path('/foo/bar/baz.py') + self.assertEqual(name, 'bar.baz') + + if not __debug__: + # asserts are off + return + + with self.assertRaises(AssertionError): + loader._get_name_from_path('/bar/baz.py') + + def test_find_tests(self): + loader = unittest.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir', + 'test.foo', 'test-not-a-module.py', 'another_dir'], + ['test4.py', 'test3.py', ]] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + def isdir(path): + return path.endswith('dir') + os.path.isdir = isdir + self.addCleanup(restore_isdir) + + def isfile(path): + # another_dir is not a package and so shouldn't be recursed into + return not path.endswith('dir') and not 'another_dir' in path + os.path.isfile = isfile + self.addCleanup(restore_isfile) + + loader._get_module_from_name = lambda path: path + ' module' + orig_load_tests = loader.loadTestsFromModule + def loadTestsFromModule(module, pattern=None): + # This is where load_tests is called. + base = orig_load_tests(module, pattern=pattern) + return base + [module + ' tests'] + loader.loadTestsFromModule = loadTestsFromModule + loader.suiteClass = lambda thing: thing + + top_level = os.path.abspath('/foo') + loader._top_level_dir = top_level + suite = list(loader._find_tests(top_level, 'test*.py')) + + # The test suites found should be sorted alphabetically for reliable + # execution order. + expected = [[name + ' module tests'] for name in + ('test1', 'test2', 'test_dir')] + expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in + ('test3', 'test4')]) + self.assertEqual(suite, expected) + + def test_find_tests_socket(self): + # A socket is neither a directory nor a regular file. + # https://bugs.python.org/issue25320 + loader = unittest.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + path_lists = [['socket']] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + os.path.isdir = lambda path: False + self.addCleanup(restore_isdir) + + os.path.isfile = lambda path: False + self.addCleanup(restore_isfile) + + loader._get_module_from_name = lambda path: path + ' module' + orig_load_tests = loader.loadTestsFromModule + def loadTestsFromModule(module, pattern=None): + # This is where load_tests is called. + base = orig_load_tests(module, pattern=pattern) + return base + [module + ' tests'] + loader.loadTestsFromModule = loadTestsFromModule + loader.suiteClass = lambda thing: thing + + top_level = os.path.abspath('/foo') + loader._top_level_dir = top_level + suite = list(loader._find_tests(top_level, 'test*.py')) + + self.assertEqual(suite, []) + + def test_find_tests_with_package(self): + loader = unittest.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + directories = ['a_directory', 'test_directory', 'test_directory2'] + path_lists = [directories, [], [], []] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + os.path.isdir = lambda path: True + self.addCleanup(restore_isdir) + + os.path.isfile = lambda path: os.path.basename(path) not in directories + self.addCleanup(restore_isfile) + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + self.paths.append(path) + if os.path.basename(path) == 'test_directory': + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + return [self.path + ' load_tests'] + self.load_tests = load_tests + + def __eq__(self, other): + return self.path == other.path + + loader._get_module_from_name = lambda name: Module(name) + orig_load_tests = loader.loadTestsFromModule + def loadTestsFromModule(module, pattern=None): + # This is where load_tests is called. + base = orig_load_tests(module, pattern=pattern) + return base + [module.path + ' module tests'] + loader.loadTestsFromModule = loadTestsFromModule + loader.suiteClass = lambda thing: thing + + loader._top_level_dir = '/foo' + # this time no '.py' on the pattern so that it can match + # a test package + suite = list(loader._find_tests('/foo', 'test*')) + + # We should have loaded tests from the a_directory and test_directory2 + # directly and via load_tests for the test_directory package, which + # still calls the baseline module loader. + self.assertEqual(suite, + [['a_directory module tests'], + ['test_directory load_tests', + 'test_directory module tests'], + ['test_directory2 module tests']]) + + + # The test module paths should be sorted for reliable execution order + self.assertEqual(Module.paths, + ['a_directory', 'test_directory', 'test_directory2']) + + # load_tests should have been called once with loader, tests and pattern + # (but there are no tests in our stub module itself, so that is [] at + # the time of call). + self.assertEqual(Module.load_tests_args, + [(loader, [], 'test*')]) + + def test_find_tests_default_calls_package_load_tests(self): + loader = unittest.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + directories = ['a_directory', 'test_directory', 'test_directory2'] + path_lists = [directories, [], [], []] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + os.path.isdir = lambda path: True + self.addCleanup(restore_isdir) + + os.path.isfile = lambda path: os.path.basename(path) not in directories + self.addCleanup(restore_isfile) + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + self.paths.append(path) + if os.path.basename(path) == 'test_directory': + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + return [self.path + ' load_tests'] + self.load_tests = load_tests + + def __eq__(self, other): + return self.path == other.path + + loader._get_module_from_name = lambda name: Module(name) + orig_load_tests = loader.loadTestsFromModule + def loadTestsFromModule(module, pattern=None): + # This is where load_tests is called. + base = orig_load_tests(module, pattern=pattern) + return base + [module.path + ' module tests'] + loader.loadTestsFromModule = loadTestsFromModule + loader.suiteClass = lambda thing: thing + + loader._top_level_dir = '/foo' + # this time no '.py' on the pattern so that it can match + # a test package + suite = list(loader._find_tests('/foo', 'test*.py')) + + # We should have loaded tests from the a_directory and test_directory2 + # directly and via load_tests for the test_directory package, which + # still calls the baseline module loader. + self.assertEqual(suite, + [['a_directory module tests'], + ['test_directory load_tests', + 'test_directory module tests'], + ['test_directory2 module tests']]) + # The test module paths should be sorted for reliable execution order + self.assertEqual(Module.paths, + ['a_directory', 'test_directory', 'test_directory2']) + + + # load_tests should have been called once with loader, tests and pattern + self.assertEqual(Module.load_tests_args, + [(loader, [], 'test*.py')]) + + def test_find_tests_customize_via_package_pattern(self): + # This test uses the example 'do-nothing' load_tests from + # https://docs.python.org/3/library/unittest.html#load-tests-protocol + # to make sure that that actually works. + # Housekeeping + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + self.addCleanup(restore_listdir) + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + self.addCleanup(restore_isfile) + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + self.addCleanup(restore_isdir) + self.addCleanup(sys.path.remove, abspath('/foo')) + + # Test data: we expect the following: + # a listdir to find our package, and isfile and isdir checks on it. + # a module-from-name call to turn that into a module + # followed by load_tests. + # then our load_tests will call discover() which is messy + # but that finally chains into find_tests again for the child dir - + # which is why we don't have an infinite loop. + # We expect to see: + # the module load tests for both package and plain module called, + # and the plain module result nested by the package module load_tests + # indicating that it was processed and could have been mutated. + vfs = {abspath('/foo'): ['my_package'], + abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} + def list_dir(path): + return list(vfs[path]) + os.listdir = list_dir + os.path.isdir = lambda path: not path.endswith('.py') + os.path.isfile = lambda path: path.endswith('.py') + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + self.paths.append(path) + if path.endswith('test_module'): + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + return [self.path + ' load_tests'] + else: + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + # top level directory cached on loader instance + __file__ = '/foo/my_package/__init__.py' + this_dir = os.path.dirname(__file__) + pkg_tests = loader.discover( + start_dir=this_dir, pattern=pattern) + return [self.path + ' load_tests', tests + ] + pkg_tests + self.load_tests = load_tests + + def __eq__(self, other): + return self.path == other.path + + loader = unittest.TestLoader() + loader._get_module_from_name = lambda name: Module(name) + loader.suiteClass = lambda thing: thing + + loader._top_level_dir = abspath('/foo') + # this time no '.py' on the pattern so that it can match + # a test package + suite = list(loader._find_tests(abspath('/foo'), 'test*.py')) + + # We should have loaded tests from both my_package and + # my_package.test_module, and also run the load_tests hook in both. + # (normally this would be nested TestSuites.) + self.assertEqual(suite, + [['my_package load_tests', [], + ['my_package.test_module load_tests']]]) + # Parents before children. + self.assertEqual(Module.paths, + ['my_package', 'my_package.test_module']) + + # load_tests should have been called twice with loader, tests and pattern + self.assertEqual(Module.load_tests_args, + [(loader, [], 'test*.py'), + (loader, [], 'test*.py')]) + + def test_discover(self): + loader = unittest.TestLoader() + + original_isfile = os.path.isfile + original_isdir = os.path.isdir + def restore_isfile(): + os.path.isfile = original_isfile + + os.path.isfile = lambda path: False + self.addCleanup(restore_isfile) + + orig_sys_path = sys.path[:] + def restore_path(): + sys.path[:] = orig_sys_path + self.addCleanup(restore_path) + + full_path = os.path.abspath(os.path.normpath('/foo')) + with self.assertRaises(ImportError): + loader.discover('/foo/bar', top_level_dir='/foo') + + self.assertEqual(loader._top_level_dir, full_path) + self.assertIn(full_path, sys.path) + + os.path.isfile = lambda path: True + os.path.isdir = lambda path: True + + def restore_isdir(): + os.path.isdir = original_isdir + self.addCleanup(restore_isdir) + + _find_tests_args = [] + def _find_tests(start_dir, pattern, namespace=None): + _find_tests_args.append((start_dir, pattern)) + return ['tests'] + loader._find_tests = _find_tests + loader.suiteClass = str + + suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar') + + top_level_dir = os.path.abspath('/foo/bar') + start_dir = os.path.abspath('/foo/bar/baz') + self.assertEqual(suite, "['tests']") + self.assertEqual(loader._top_level_dir, top_level_dir) + self.assertEqual(_find_tests_args, [(start_dir, 'pattern')]) + self.assertIn(top_level_dir, sys.path) + + def test_discover_start_dir_is_package_calls_package_load_tests(self): + # This test verifies that the package load_tests in a package is indeed + # invoked when the start_dir is a package (and not the top level). + # http://bugs.python.org/issue22457 + + # Test data: we expect the following: + # an isfile to verify the package, then importing and scanning + # as per _find_tests' normal behaviour. + # We expect to see our load_tests hook called once. + vfs = {abspath('/toplevel'): ['startdir'], + abspath('/toplevel/startdir'): ['__init__.py']} + def list_dir(path): + return list(vfs[path]) + self.addCleanup(setattr, os, 'listdir', os.listdir) + os.listdir = list_dir + self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) + os.path.isfile = lambda path: path.endswith('.py') + self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) + os.path.isdir = lambda path: not path.endswith('.py') + self.addCleanup(sys.path.remove, abspath('/toplevel')) + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + + def load_tests(self, loader, tests, pattern): + return ['load_tests called ' + self.path] + + def __eq__(self, other): + return self.path == other.path + + loader = unittest.TestLoader() + loader._get_module_from_name = lambda name: Module(name) + loader.suiteClass = lambda thing: thing + + suite = loader.discover('/toplevel/startdir', top_level_dir='/toplevel') + + # We should have loaded tests from the package __init__. + # (normally this would be nested TestSuites.) + self.assertEqual(suite, + [['load_tests called startdir']]) + + def setup_import_issue_tests(self, fakefile): + listdir = os.listdir + os.listdir = lambda _: [fakefile] + isfile = os.path.isfile + os.path.isfile = lambda _: True + orig_sys_path = sys.path[:] + def restore(): + os.path.isfile = isfile + os.listdir = listdir + sys.path[:] = orig_sys_path + self.addCleanup(restore) + + def setup_import_issue_package_tests(self, vfs): + self.addCleanup(setattr, os, 'listdir', os.listdir) + self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) + self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) + self.addCleanup(sys.path.__setitem__, slice(None), list(sys.path)) + def list_dir(path): + return list(vfs[path]) + os.listdir = list_dir + os.path.isdir = lambda path: not path.endswith('.py') + os.path.isfile = lambda path: path.endswith('.py') + + def test_discover_with_modules_that_fail_to_import(self): + loader = unittest.TestLoader() + + self.setup_import_issue_tests('test_this_does_not_exist.py') + + suite = loader.discover('.') + self.assertIn(os.getcwd(), sys.path) + self.assertEqual(suite.countTestCases(), 1) + # Errors loading the suite are also captured for introspection. + self.assertNotEqual([], loader.errors) + self.assertEqual(1, len(loader.errors)) + error = loader.errors[0] + self.assertTrue( + 'Failed to import test module: test_this_does_not_exist' in error, + 'missing error string in %r' % error) + test = list(list(suite)[0])[0] # extract test from suite + + with self.assertRaises(ImportError): + test.test_this_does_not_exist() + + def test_discover_with_init_modules_that_fail_to_import(self): + vfs = {abspath('/foo'): ['my_package'], + abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} + self.setup_import_issue_package_tests(vfs) + import_calls = [] + def _get_module_from_name(name): + import_calls.append(name) + raise ImportError("Cannot import Name") + loader = unittest.TestLoader() + loader._get_module_from_name = _get_module_from_name + suite = loader.discover(abspath('/foo')) + + self.assertIn(abspath('/foo'), sys.path) + self.assertEqual(suite.countTestCases(), 1) + # Errors loading the suite are also captured for introspection. + self.assertNotEqual([], loader.errors) + self.assertEqual(1, len(loader.errors)) + error = loader.errors[0] + self.assertTrue( + 'Failed to import test module: my_package' in error, + 'missing error string in %r' % error) + test = list(list(suite)[0])[0] # extract test from suite + with self.assertRaises(ImportError): + test.my_package() + self.assertEqual(import_calls, ['my_package']) + + # Check picklability + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickle.loads(pickle.dumps(test, proto)) + + def test_discover_with_module_that_raises_SkipTest_on_import(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + loader = unittest.TestLoader() + + def _get_module_from_name(name): + raise unittest.SkipTest('skipperoo') + loader._get_module_from_name = _get_module_from_name + + self.setup_import_issue_tests('test_skip_dummy.py') + + suite = loader.discover('.') + self.assertEqual(suite.countTestCases(), 1) + + result = unittest.TestResult() + suite.run(result) + self.assertEqual(len(result.skipped), 1) + + # Check picklability + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickle.loads(pickle.dumps(suite, proto)) + + def test_discover_with_init_module_that_raises_SkipTest_on_import(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + vfs = {abspath('/foo'): ['my_package'], + abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} + self.setup_import_issue_package_tests(vfs) + import_calls = [] + def _get_module_from_name(name): + import_calls.append(name) + raise unittest.SkipTest('skipperoo') + loader = unittest.TestLoader() + loader._get_module_from_name = _get_module_from_name + suite = loader.discover(abspath('/foo')) + + self.assertIn(abspath('/foo'), sys.path) + self.assertEqual(suite.countTestCases(), 1) + result = unittest.TestResult() + suite.run(result) + self.assertEqual(len(result.skipped), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(import_calls, ['my_package']) + + # Check picklability + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickle.loads(pickle.dumps(suite, proto)) + + def test_command_line_handling_parseArgs(self): + program = TestableTestProgram() + + args = [] + program._do_discovery = args.append + program.parseArgs(['something', 'discover']) + self.assertEqual(args, [[]]) + + args[:] = [] + program.parseArgs(['something', 'discover', 'foo', 'bar']) + self.assertEqual(args, [['foo', 'bar']]) + + def test_command_line_handling_discover_by_default(self): + program = TestableTestProgram() + + args = [] + program._do_discovery = args.append + program.parseArgs(['something']) + self.assertEqual(args, [[]]) + self.assertEqual(program.verbosity, 1) + self.assertIs(program.buffer, False) + self.assertIs(program.catchbreak, False) + self.assertIs(program.failfast, False) + + def test_command_line_handling_discover_by_default_with_options(self): + program = TestableTestProgram() + + args = [] + program._do_discovery = args.append + program.parseArgs(['something', '-v', '-b', '-v', '-c', '-f']) + self.assertEqual(args, [[]]) + self.assertEqual(program.verbosity, 2) + self.assertIs(program.buffer, True) + self.assertIs(program.catchbreak, True) + self.assertIs(program.failfast, True) + + + def test_command_line_handling_do_discovery_too_many_arguments(self): + program = TestableTestProgram() + program.testLoader = None + + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: + # too many args + program._do_discovery(['one', 'two', 'three', 'four']) + self.assertEqual(cm.exception.args, (2,)) + self.assertIn('usage:', stderr.getvalue()) + + + def test_command_line_handling_do_discovery_uses_default_loader(self): + program = object.__new__(unittest.TestProgram) + program._initArgParsers() + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program.testLoader = Loader() + program._do_discovery(['-v']) + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + def test_command_line_handling_do_discovery_calls_loader(self): + program = TestableTestProgram() + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program._do_discovery(['-v'], Loader=Loader) + self.assertEqual(program.verbosity, 2) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['--verbose'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery([], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['fish', 'eggs'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['-s', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['-t', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')]) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['-p', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'fish', None)]) + self.assertFalse(program.failfast) + self.assertFalse(program.catchbreak) + + Loader.args = [] + program = TestableTestProgram() + program._do_discovery(['-p', 'eggs', '-s', 'fish', '-v', '-f', '-c'], + Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + self.assertEqual(program.verbosity, 2) + self.assertTrue(program.failfast) + self.assertTrue(program.catchbreak) + + def setup_module_clash(self): + class Module(object): + __file__ = 'bar/foo.py' + sys.modules['foo'] = Module + full_path = os.path.abspath('foo') + original_listdir = os.listdir + original_isfile = os.path.isfile + original_isdir = os.path.isdir + original_realpath = os.path.realpath + + def cleanup(): + os.listdir = original_listdir + os.path.isfile = original_isfile + os.path.isdir = original_isdir + os.path.realpath = original_realpath + del sys.modules['foo'] + if full_path in sys.path: + sys.path.remove(full_path) + self.addCleanup(cleanup) + + def listdir(_): + return ['foo.py'] + def isfile(_): + return True + def isdir(_): + return True + os.listdir = listdir + os.path.isfile = isfile + os.path.isdir = isdir + if os.name == 'nt': + # ntpath.realpath may inject path prefixes when failing to + # resolve real files, so we substitute abspath() here instead. + os.path.realpath = os.path.abspath + return full_path + + def test_detect_module_clash(self): + full_path = self.setup_module_clash() + loader = unittest.TestLoader() + + mod_dir = os.path.abspath('bar') + expected_dir = os.path.abspath('foo') + msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?" % (mod_dir, expected_dir)) + self.assertRaisesRegex( + ImportError, '^%s$' % msg, loader.discover, + start_dir='foo', pattern='foo.py' + ) + self.assertEqual(sys.path[0], full_path) + + def test_module_symlink_ok(self): + full_path = self.setup_module_clash() + + original_realpath = os.path.realpath + + mod_dir = os.path.abspath('bar') + expected_dir = os.path.abspath('foo') + + def cleanup(): + os.path.realpath = original_realpath + self.addCleanup(cleanup) + + def realpath(path): + if path == os.path.join(mod_dir, 'foo.py'): + return os.path.join(expected_dir, 'foo.py') + return path + os.path.realpath = realpath + loader = unittest.TestLoader() + loader.discover(start_dir='foo', pattern='foo.py') + + def test_discovery_from_dotted_path(self): + loader = unittest.TestLoader() + + tests = [self] + expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__)) + + self.wasRun = False + def _find_tests(start_dir, pattern, namespace=None): + self.wasRun = True + self.assertEqual(start_dir, expectedPath) + return tests + loader._find_tests = _find_tests + suite = loader.discover('unittest.test') + self.assertTrue(self.wasRun) + self.assertEqual(suite._tests, tests) + + + def test_discovery_from_dotted_path_builtin_modules(self): + + loader = unittest.TestLoader() + + listdir = os.listdir + os.listdir = lambda _: ['test_this_does_not_exist.py'] + isfile = os.path.isfile + isdir = os.path.isdir + os.path.isdir = lambda _: False + orig_sys_path = sys.path[:] + def restore(): + os.path.isfile = isfile + os.path.isdir = isdir + os.listdir = listdir + sys.path[:] = orig_sys_path + self.addCleanup(restore) + + with self.assertRaises(TypeError) as cm: + loader.discover('sys') + self.assertEqual(str(cm.exception), + 'Can not use builtin modules ' + 'as dotted module names') + + def test_discovery_from_dotted_namespace_packages(self): + loader = unittest.TestLoader() + + package = types.ModuleType('package') + package.__path__ = ['/a', '/b'] + package.__spec__ = types.SimpleNamespace( + loader=None, + submodule_search_locations=['/a', '/b'] + ) + + def _import(packagename, *args, **kwargs): + sys.modules[packagename] = package + return package + + _find_tests_args = [] + def _find_tests(start_dir, pattern, namespace=None): + _find_tests_args.append((start_dir, pattern)) + return ['%s/tests' % start_dir] + + loader._find_tests = _find_tests + loader.suiteClass = list + + with unittest.mock.patch('builtins.__import__', _import): + # Since loader.discover() can modify sys.path, restore it when done. + with support.DirsOnSysPath(): + # Make sure to remove 'package' from sys.modules when done. + with test.test_importlib.util.uncache('package'): + suite = loader.discover('package') + + self.assertEqual(suite, ['/a/tests', '/b/tests']) + + def test_discovery_failed_discovery(self): + loader = unittest.TestLoader() + package = types.ModuleType('package') + + def _import(packagename, *args, **kwargs): + sys.modules[packagename] = package + return package + + with unittest.mock.patch('builtins.__import__', _import): + # Since loader.discover() can modify sys.path, restore it when done. + with support.DirsOnSysPath(): + # Make sure to remove 'package' from sys.modules when done. + with test.test_importlib.util.uncache('package'): + with self.assertRaises(TypeError) as cm: + loader.discover('package') + self.assertEqual(str(cm.exception), + 'don\'t know how to discover from {!r}' + .format(package)) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py b/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py new file mode 100644 index 0000000000..c5f2bcbe74 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py @@ -0,0 +1,148 @@ +import unittest + +from unittest.test.support import LoggingResult + + +class Test_FunctionTestCase(unittest.TestCase): + + # "Return the number of tests represented by the this test object. For + # TestCase instances, this will always be 1" + def test_countTestCases(self): + test = unittest.FunctionTestCase(lambda: None) + + self.assertEqual(test.countTestCases(), 1) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + raise RuntimeError('raised by setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + unittest.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + raise RuntimeError('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest'] + unittest.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + self.fail('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addFailure', 'stopTest'] + unittest.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + raise RuntimeError('raised by tearDown') + + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + unittest.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + test = unittest.FunctionTestCase(lambda: None) + + self.assertIsInstance(test.id(), str) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__no_docstring(self): + test = unittest.FunctionTestCase(lambda: None) + + self.assertEqual(test.shortDescription(), None) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__singleline_docstring(self): + desc = "this tests foo" + test = unittest.FunctionTestCase(lambda: None, description=desc) + + self.assertEqual(test.shortDescription(), "this tests foo") + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_loader.py b/Monika After Story/game/python-packages/unittest/test/test_loader.py new file mode 100644 index 0000000000..bc54bf0553 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_loader.py @@ -0,0 +1,1595 @@ +import functools +import sys +import types +import warnings + +import unittest + +# Decorator used in the deprecation tests to reset the warning registry for +# test isolation and reproducibility. +def warningregistry(func): + def wrapper(*args, **kws): + missing = [] + saved = getattr(warnings, '__warningregistry__', missing).copy() + try: + return func(*args, **kws) + finally: + if saved is missing: + try: + del warnings.__warningregistry__ + except AttributeError: + pass + else: + warnings.__warningregistry__ = saved + return wrapper + + +class Test_TestLoader(unittest.TestCase): + + ### Basic object tests + ################################################################ + + def test___init__(self): + loader = unittest.TestLoader() + self.assertEqual([], loader.errors) + + ### Tests for TestLoader.loadTestsFromTestCase + ################################################################ + + # "Return a suite of all test cases contained in the TestCase-derived + # class testCaseClass" + def test_loadTestsFromTestCase(self): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Return a suite of all test cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure it does the right thing even if no tests were found + def test_loadTestsFromTestCase__no_matches(self): + class Foo(unittest.TestCase): + def foo_bar(self): pass + + empty_suite = unittest.TestSuite() + + loader = unittest.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), empty_suite) + + # "Return a suite of all test cases contained in the TestCase-derived + # class testCaseClass" + # + # What happens if loadTestsFromTestCase() is given an object + # that isn't a subclass of TestCase? Specifically, what happens + # if testCaseClass is a subclass of TestSuite? + # + # This is checked for specifically in the code, so we better add a + # test for it. + def test_loadTestsFromTestCase__TestSuite_subclass(self): + class NotATestCase(unittest.TestSuite): + pass + + loader = unittest.TestLoader() + try: + loader.loadTestsFromTestCase(NotATestCase) + except TypeError: + pass + else: + self.fail('Should raise TypeError') + + # "Return a suite of all test cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure loadTestsFromTestCase() picks up the default test method + # name (as specified by TestCase), even though the method name does + # not match the default TestLoader.testMethodPrefix string + def test_loadTestsFromTestCase__default_method_name(self): + class Foo(unittest.TestCase): + def runTest(self): + pass + + loader = unittest.TestLoader() + # This has to be false for the test to succeed + self.assertFalse('runTest'.startswith(loader.testMethodPrefix)) + + suite = loader.loadTestsFromTestCase(Foo) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [Foo('runTest')]) + + ################################################################ + ### /Tests for TestLoader.loadTestsFromTestCase + + ### Tests for TestLoader.loadTestsFromModule + ################################################################ + + # "This method searches `module` for classes derived from TestCase" + def test_loadTestsFromModule__TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = [loader.suiteClass([MyTestCase('test')])] + self.assertEqual(list(suite), expected) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (no TestCase instances)? + def test_loadTestsFromModule__no_TestCase_instances(self): + m = types.ModuleType('m') + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (TestCases instances, but no tests)? + def test_loadTestsFromModule__no_TestCase_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [loader.suiteClass()]) + + # "This method searches `module` for classes derived from TestCase"s + # + # What happens if loadTestsFromModule() is given something other + # than a module? + # + # XXX Currently, it succeeds anyway. This flexibility + # should either be documented or loadTestsFromModule() should + # raise a TypeError + # + # XXX Certain people are using this behaviour. We'll add a test for it + def test_loadTestsFromModule__not_a_module(self): + class MyTestCase(unittest.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(NotAModule) + + reference = [unittest.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + + # Check that loadTestsFromModule honors (or not) a module + # with a load_tests function. + @warningregistry + def test_loadTestsFromModule__load_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest.TestSuite) + self.assertEqual(load_tests_args, [loader, suite, None]) + # With Python 3.5, the undocumented and unofficial use_load_tests is + # ignored (and deprecated). + load_tests_args = [] + with warnings.catch_warnings(record=False): + warnings.simplefilter('ignore') + suite = loader.loadTestsFromModule(m, use_load_tests=False) + self.assertEqual(load_tests_args, [loader, suite, None]) + + @warningregistry + def test_loadTestsFromModule__use_load_tests_deprecated_positional(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + # The method still works. + loader = unittest.TestLoader() + # use_load_tests=True as a positional argument. + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + suite = loader.loadTestsFromModule(m, False) + self.assertIsInstance(suite, unittest.TestSuite) + # load_tests was still called because use_load_tests is deprecated + # and ignored. + self.assertEqual(load_tests_args, [loader, suite, None]) + # We got a warning. + self.assertIs(w[-1].category, DeprecationWarning) + self.assertEqual(str(w[-1].message), + 'use_load_tests is deprecated and ignored') + + @warningregistry + def test_loadTestsFromModule__use_load_tests_deprecated_keyword(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + # The method still works. + loader = unittest.TestLoader() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + suite = loader.loadTestsFromModule(m, use_load_tests=False) + self.assertIsInstance(suite, unittest.TestSuite) + # load_tests was still called because use_load_tests is deprecated + # and ignored. + self.assertEqual(load_tests_args, [loader, suite, None]) + # We got a warning. + self.assertIs(w[-1].category, DeprecationWarning) + self.assertEqual(str(w[-1].message), + 'use_load_tests is deprecated and ignored') + + @warningregistry + def test_loadTestsFromModule__too_many_positional_args(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + loader = unittest.TestLoader() + with self.assertRaises(TypeError) as cm, \ + warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + loader.loadTestsFromModule(m, False, 'testme.*') + # We still got the deprecation warning. + self.assertIs(w[-1].category, DeprecationWarning) + self.assertEqual(str(w[-1].message), + 'use_load_tests is deprecated and ignored') + # We also got a TypeError for too many positional arguments. + self.assertEqual(type(cm.exception), TypeError) + self.assertEqual( + str(cm.exception), + 'loadTestsFromModule() takes 1 positional argument but 3 were given') + + @warningregistry + def test_loadTestsFromModule__use_load_tests_other_bad_keyword(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + loader = unittest.TestLoader() + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + with self.assertRaises(TypeError) as cm: + loader.loadTestsFromModule( + m, use_load_tests=False, very_bad=True, worse=False) + self.assertEqual(type(cm.exception), TypeError) + # The error message names the first bad argument alphabetically, + # however use_load_tests (which sorts first) is ignored. + self.assertEqual( + str(cm.exception), + "loadTestsFromModule() got an unexpected keyword argument 'very_bad'") + + def test_loadTestsFromModule__pattern(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m, pattern='testme.*') + self.assertIsInstance(suite, unittest.TestSuite) + self.assertEqual(load_tests_args, [loader, suite, 'testme.*']) + + def test_loadTestsFromModule__faulty_load_tests(self): + m = types.ModuleType('m') + + def load_tests(loader, tests, pattern): + raise TypeError('some failure') + m.load_tests = load_tests + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest.TestSuite) + self.assertEqual(suite.countTestCases(), 1) + # Errors loading the suite are also captured for introspection. + self.assertNotEqual([], loader.errors) + self.assertEqual(1, len(loader.errors)) + error = loader.errors[0] + self.assertTrue( + 'Failed to call load_tests:' in error, + 'missing error string in %r' % error) + test = list(suite)[0] + + self.assertRaisesRegex(TypeError, "some failure", test.m) + + ################################################################ + ### /Tests for TestLoader.loadTestsFromModule() + + ### Tests for TestLoader.loadTestsFromName() + ################################################################ + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromName__empty_name(self): + loader = unittest.TestLoader() + + try: + loader.loadTestsFromName('') + except ValueError as e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the name contains invalid characters? + def test_loadTestsFromName__malformed_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('abc () //') + error, test = self.check_deferred_error(loader, suite) + expected = "Failed to import test module: abc () //" + expected_regex = r"Failed to import test module: abc \(\) //" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex( + ImportError, expected_regex, getattr(test, 'abc () //')) + + # "The specifier name is a ``dotted name'' that may resolve ... to a + # module" + # + # What happens when a module by that name can't be found? + def test_loadTestsFromName__unknown_module_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('sdasfasfasdf') + expected = "No module named 'sdasfasfasdf'" + error, test = self.check_deferred_error(loader, suite) + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module is found, but the attribute isn't? + def test_loadTestsFromName__unknown_attr_name_on_module(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('unittest.loader.sdasfasfasdf') + expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" + error, test = self.check_deferred_error(loader, suite) + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module is found, but the attribute isn't? + def test_loadTestsFromName__unknown_attr_name_on_package(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('unittest.sdasfasfasdf') + expected = "No module named 'unittest.sdasfasfasdf'" + error, test = self.check_deferred_error(loader, suite) + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when we provide the module, but the attribute can't be + # found? + def test_loadTestsFromName__relative_unknown_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('sdasfasfasdf', unittest) + expected = "module 'unittest' has no attribute 'sdasfasfasdf'" + error, test = self.check_deferred_error(loader, suite) + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise ValueError when passed an empty + # name relative to a provided module? + # + # XXX Should probably raise a ValueError instead of an AttributeError + def test_loadTestsFromName__relative_empty_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromName('', unittest) + error, test = self.check_deferred_error(loader, suite) + expected = "has no attribute ''" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, getattr(test, '')) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when an impossible name is given, relative to the provided + # `module`? + def test_loadTestsFromName__relative_malformed_name(self): + loader = unittest.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + suite = loader.loadTestsFromName('abc () //', unittest) + error, test = self.check_deferred_error(loader, suite) + expected = "module 'unittest' has no attribute 'abc () //'" + expected_regex = r"module 'unittest' has no attribute 'abc \(\) //'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex( + AttributeError, expected_regex, getattr(test, 'abc () //')) + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise TypeError when the `module` argument + # isn't a module object? + # + # XXX Accepts the not-a-module object, ignoring the object's type + # This should raise an exception or the method name should be changed + # + # XXX Some people are relying on this, so keep it for now + def test_loadTestsFromName__relative_not_a_module(self): + class MyTestCase(unittest.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('test_2', NotAModule) + + reference = [MyTestCase('test')] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromName__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest.TestLoader() + try: + loader.loadTestsFromName('testcase_1', m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may + # resolve either to ... a test case class" + def test_loadTestsFromName__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('testcase_1', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + def test_loadTestsFromName__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testsuite = unittest.TestSuite([MyTestCase('test')]) + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('testsuite', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + def test_loadTestsFromName__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does loadTestsFromName() raise the proper exception when trying to + # resolve "a test method within a test case class" that doesn't exist + # for the given name (relative to a provided module)? + def test_loadTestsFromName__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('testcase_1.testfoo', m) + expected = "type object 'MyTestCase' has no attribute 'testfoo'" + error, test = self.check_deferred_error(loader, suite) + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.testfoo) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromName__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest.FunctionTestCase(lambda: None) + testcase_2 = unittest.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('return_TestSuite', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1, testcase_2]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromName__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__callable__TestCase_instance_ProperSuiteClass(self): + class SubTestSuite(unittest.TestSuite): + pass + m = types.ModuleType('m') + testcase_1 = unittest.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest.TestLoader() + loader.suiteClass = SubTestSuite + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__relative_testmethod_ProperSuiteClass(self): + class SubTestSuite(unittest.TestSuite): + pass + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + loader.suiteClass=SubTestSuite + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens if the callable returns something else? + def test_loadTestsFromName__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest.TestLoader() + try: + suite = loader.loadTestsFromName('return_wrong', m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromName__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest.TestLoader() + try: + suite = loader.loadTestsFromName(module_name) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### Tests for TestLoader.loadTestsFromName() + + ### Tests for TestLoader.loadTestsFromNames() + ################################################################ + + def check_deferred_error(self, loader, suite): + """Helper function for checking that errors in loading are reported. + + :param loader: A loader with some errors. + :param suite: A suite that should have a late bound error. + :return: The first error message from the loader and the test object + from the suite. + """ + self.assertIsInstance(suite, unittest.TestSuite) + self.assertEqual(suite.countTestCases(), 1) + # Errors loading the suite are also captured for introspection. + self.assertNotEqual([], loader.errors) + self.assertEqual(1, len(loader.errors)) + error = loader.errors[0] + test = list(suite)[0] + return error, test + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # + # What happens if that sequence of names is empty? + def test_loadTestsFromNames__empty_name_list(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames([]) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens if that sequence of names is empty? + # + # XXX Should this raise a ValueError or just return an empty TestSuite? + def test_loadTestsFromNames__relative_empty_name_list(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames([], unittest) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromNames__empty_name(self): + loader = unittest.TestLoader() + + try: + loader.loadTestsFromNames(['']) + except ValueError as e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when presented with an impossible module name? + def test_loadTestsFromNames__malformed_name(self): + loader = unittest.TestLoader() + + # XXX Should this raise ValueError or ImportError? + suite = loader.loadTestsFromNames(['abc () //']) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "Failed to import test module: abc () //" + expected_regex = r"Failed to import test module: abc \(\) //" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex( + ImportError, expected_regex, getattr(test, 'abc () //')) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when no module can be found for the given name? + def test_loadTestsFromNames__unknown_module_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames(['sdasfasfasdf']) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "Failed to import test module: sdasfasfasdf" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module can be found, but not the attribute? + def test_loadTestsFromNames__unknown_attr_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames( + ['unittest.loader.sdasfasfasdf', 'unittest.test.dummy']) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when given an unknown attribute on a specified `module` + # argument? + def test_loadTestsFromNames__unknown_name_relative_1(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames(['sdasfasfasdf'], unittest) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "module 'unittest' has no attribute 'sdasfasfasdf'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Do unknown attributes (relative to a provided module) still raise an + # exception even in the presence of valid attribute names? + def test_loadTestsFromNames__unknown_name_relative_2(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest) + error, test = self.check_deferred_error(loader, list(suite)[1]) + expected = "module 'unittest' has no attribute 'sdasfasfasdf'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when faced with the empty string? + # + # XXX This currently raises AttributeError, though ValueError is probably + # more appropriate + def test_loadTestsFromNames__relative_empty_name(self): + loader = unittest.TestLoader() + + suite = loader.loadTestsFromNames([''], unittest) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "has no attribute ''" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, getattr(test, '')) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when presented with an impossible attribute name? + def test_loadTestsFromNames__relative_malformed_name(self): + loader = unittest.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + suite = loader.loadTestsFromNames(['abc () //'], unittest) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "module 'unittest' has no attribute 'abc () //'" + expected_regex = r"module 'unittest' has no attribute 'abc \(\) //'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex( + AttributeError, expected_regex, getattr(test, 'abc () //')) + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromNames() make sure the provided `module` is in fact + # a module? + # + # XXX This validation is currently not done. This flexibility should + # either be documented or a TypeError should be raised. + def test_loadTestsFromNames__relative_not_a_module(self): + class MyTestCase(unittest.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['test_2'], NotAModule) + + reference = [unittest.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromNames__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest.TestLoader() + try: + loader.loadTestsFromNames(['testcase_1'], m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test case class" + def test_loadTestsFromNames__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = loader.suiteClass([MyTestCase('test')]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a TestSuite instance" + def test_loadTestsFromNames__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testsuite = unittest.TestSuite([MyTestCase('test')]) + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['testsuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [m.testsuite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + def test_loadTestsFromNames__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1.test'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest.TestSuite([MyTestCase('test')]) + self.assertEqual(list(suite), [ref_suite]) + + # #14971: Make sure the dotted name resolution works even if the actual + # function doesn't have the same name as is used to find it. + def test_loadTestsFromName__function_with_different_name_than_method(self): + # lambdas have the name ''. + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + test = lambda: 1 + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1.test'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest.TestSuite([MyTestCase('test')]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + # + # Does the method gracefully handle names that initially look like they + # resolve to "a test method within a test case class" but don't? + def test_loadTestsFromNames__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1.testfoo'], m) + error, test = self.check_deferred_error(loader, list(suite)[0]) + expected = "type object 'MyTestCase' has no attribute 'testfoo'" + self.assertIn( + expected, error, + 'missing error string in %r' % error) + self.assertRaisesRegex(AttributeError, expected, test.testfoo) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromNames__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest.FunctionTestCase(lambda: None) + testcase_2 = unittest.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['return_TestSuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = unittest.TestSuite([testcase_1, testcase_2]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromNames__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['return_TestCase'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # Are staticmethods handled correctly? + def test_loadTestsFromNames__callable__call_staticmethod(self): + m = types.ModuleType('m') + class Test1(unittest.TestCase): + def test(self): + pass + + testcase_1 = Test1('test') + class Foo(unittest.TestCase): + @staticmethod + def foo(): + return testcase_1 + m.Foo = Foo + + loader = unittest.TestLoader() + suite = loader.loadTestsFromNames(['Foo.foo'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens when the callable returns something else? + def test_loadTestsFromNames__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest.TestLoader() + try: + suite = loader.loadTestsFromNames(['return_wrong'], m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromNames__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest.TestLoader() + try: + suite = loader.loadTestsFromNames([module_name]) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [unittest.TestSuite()]) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### /Tests for TestLoader.loadTestsFromNames() + + ### Tests for TestLoader.getTestCaseNames() + ################################################################ + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Test.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames(self): + class Test(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + loader = unittest.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), ['test_1', 'test_2']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Does getTestCaseNames() behave appropriately if no tests are found? + def test_getTestCaseNames__no_tests(self): + class Test(unittest.TestCase): + def foobar(self): pass + + loader = unittest.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), []) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Are not-TestCases handled gracefully? + # + # XXX This should raise a TypeError, not return a list + # + # XXX It's too late in the 2.5 release cycle to fix this, but it should + # probably be revisited for 2.6 + def test_getTestCaseNames__not_a_TestCase(self): + class BadCase(int): + def test_foo(self): + pass + + loader = unittest.TestLoader() + names = loader.getTestCaseNames(BadCase) + + self.assertEqual(names, ['test_foo']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Make sure inherited names are handled. + # + # TestP.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames__inheritance(self): + class TestP(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + class TestC(TestP): + def test_1(self): pass + def test_3(self): pass + + loader = unittest.TestLoader() + + names = ['test_1', 'test_2', 'test_3'] + self.assertEqual(loader.getTestCaseNames(TestC), names) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # If TestLoader.testNamePatterns is set, only tests that match one of these + # patterns should be included. + def test_getTestCaseNames__testNamePatterns(self): + class MyTest(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + loader = unittest.TestLoader() + + loader.testNamePatterns = [] + self.assertEqual(loader.getTestCaseNames(MyTest), []) + + loader.testNamePatterns = ['*1'] + self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1']) + + loader.testNamePatterns = ['*1', '*2'] + self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1', 'test_2']) + + loader.testNamePatterns = ['*My*'] + self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1', 'test_2']) + + loader.testNamePatterns = ['*my*'] + self.assertEqual(loader.getTestCaseNames(MyTest), []) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # If TestLoader.testNamePatterns is set, only tests that match one of these + # patterns should be included. + # + # For backwards compatibility reasons (see bpo-32071), the check may only + # touch a TestCase's attribute if it starts with the test method prefix. + def test_getTestCaseNames__testNamePatterns__attribute_access_regression(self): + class Trap: + def __get__(*ignored): + self.fail('Non-test attribute accessed') + + class MyTest(unittest.TestCase): + def test_1(self): pass + foobar = Trap() + + loader = unittest.TestLoader() + self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1']) + + loader = unittest.TestLoader() + loader.testNamePatterns = [] + self.assertEqual(loader.getTestCaseNames(MyTest), []) + + ################################################################ + ### /Tests for TestLoader.getTestCaseNames() + + ### Tests for TestLoader.testMethodPrefix + ################################################################ + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromTestCase(self): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests_1 = unittest.TestSuite([Foo('foo_bar')]) + tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = [unittest.TestSuite([Foo('foo_bar')])] + tests_2 = [unittest.TestSuite([Foo('test_1'), Foo('test_2')])] + + loader = unittest.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest.TestSuite([Foo('foo_bar')]) + tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest.TestSuite([unittest.TestSuite([Foo('foo_bar')])]) + tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) + tests_2 = unittest.TestSuite([tests_2]) + + loader = unittest.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_2) + + # "The default value is 'test'" + def test_testMethodPrefix__default_value(self): + loader = unittest.TestLoader() + self.assertEqual(loader.testMethodPrefix, 'test') + + ################################################################ + ### /Tests for TestLoader.testMethodPrefix + + ### Tests for TestLoader.sortTestMethodsUsing + ################################################################ + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromTestCase(self): + def reversed_cmp(x, y): + return -((x > y) - (x < y)) + + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromModule(self): + def reversed_cmp(x, y): + return -((x > y) - (x < y)) + + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromModule(m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromName(self): + def reversed_cmp(x, y): + return -((x > y) - (x < y)) + + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromNames(self): + def reversed_cmp(x, y): + return -((x > y) - (x < y)) + + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromNames(['Foo'], m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames()" + # + # Does it actually affect getTestCaseNames()? + def test_sortTestMethodsUsing__getTestCaseNames(self): + def reversed_cmp(x, y): + return -((x > y) - (x < y)) + + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + test_names = ['test_2', 'test_1'] + self.assertEqual(loader.getTestCaseNames(Foo), test_names) + + # "The default value is the built-in cmp() function" + # Since cmp is now defunct, we simply verify that the results + # occur in the same order as they would with the default sort. + def test_sortTestMethodsUsing__default_value(self): + loader = unittest.TestLoader() + + class Foo(unittest.TestCase): + def test_2(self): pass + def test_3(self): pass + def test_1(self): pass + + test_names = ['test_2', 'test_3', 'test_1'] + self.assertEqual(loader.getTestCaseNames(Foo), sorted(test_names)) + + + # "it can be set to None to disable the sort." + # + # XXX How is this different from reassigning cmp? Are the tests returned + # in a random order or something? This behaviour should die + def test_sortTestMethodsUsing__None(self): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = None + + test_names = ['test_2', 'test_1'] + self.assertEqual(set(loader.getTestCaseNames(Foo)), set(test_names)) + + ################################################################ + ### /Tests for TestLoader.sortTestMethodsUsing + + ### Tests for TestLoader.suiteClass + ################################################################ + + # "Callable object that constructs a test suite from a list of tests." + def test_suiteClass__loadTestsFromTestCase(self): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromModule(m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests) + + # "The default value is the TestSuite class" + def test_suiteClass__default_value(self): + loader = unittest.TestLoader() + self.assertIs(loader.suiteClass, unittest.TestSuite) + + + def test_partial_functions(self): + def noop(arg): + pass + + class Foo(unittest.TestCase): + pass + + setattr(Foo, 'test_partial', functools.partial(noop, None)) + + loader = unittest.TestLoader() + + test_names = ['test_partial'] + self.assertEqual(loader.getTestCaseNames(Foo), test_names) + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_program.py b/Monika After Story/game/python-packages/unittest/test/test_program.py new file mode 100644 index 0000000000..eef82ff937 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_program.py @@ -0,0 +1,440 @@ +import io + +import os +import sys +import subprocess +from test import support +import unittest +import unittest.test + + +class Test_TestProgram(unittest.TestCase): + + def test_discovery_from_dotted_path(self): + loader = unittest.TestLoader() + + tests = [self] + expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__)) + + self.wasRun = False + def _find_tests(start_dir, pattern): + self.wasRun = True + self.assertEqual(start_dir, expectedPath) + return tests + loader._find_tests = _find_tests + suite = loader.discover('unittest.test') + self.assertTrue(self.wasRun) + self.assertEqual(suite._tests, tests) + + # Horrible white box test + def testNoExit(self): + result = object() + test = object() + + class FakeRunner(object): + def run(self, test): + self.test = test + return result + + runner = FakeRunner() + + oldParseArgs = unittest.TestProgram.parseArgs + def restoreParseArgs(): + unittest.TestProgram.parseArgs = oldParseArgs + unittest.TestProgram.parseArgs = lambda *args: None + self.addCleanup(restoreParseArgs) + + def removeTest(): + del unittest.TestProgram.test + unittest.TestProgram.test = test + self.addCleanup(removeTest) + + program = unittest.TestProgram(testRunner=runner, exit=False, verbosity=2) + + self.assertEqual(program.result, result) + self.assertEqual(runner.test, test) + self.assertEqual(program.verbosity, 2) + + class FooBar(unittest.TestCase): + def testPass(self): + assert True + def testFail(self): + assert False + + class FooBarLoader(unittest.TestLoader): + """Test loader that returns a suite containing FooBar.""" + def loadTestsFromModule(self, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + def loadTestsFromNames(self, names, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + def test_defaultTest_with_string(self): + class FakeRunner(object): + def run(self, test): + self.test = test + return True + + old_argv = sys.argv + sys.argv = ['faketest'] + runner = FakeRunner() + program = unittest.TestProgram(testRunner=runner, exit=False, + defaultTest='unittest.test', + testLoader=self.FooBarLoader()) + sys.argv = old_argv + self.assertEqual(('unittest.test',), program.testNames) + + def test_defaultTest_with_iterable(self): + class FakeRunner(object): + def run(self, test): + self.test = test + return True + + old_argv = sys.argv + sys.argv = ['faketest'] + runner = FakeRunner() + program = unittest.TestProgram( + testRunner=runner, exit=False, + defaultTest=['unittest.test', 'unittest.test2'], + testLoader=self.FooBarLoader()) + sys.argv = old_argv + self.assertEqual(['unittest.test', 'unittest.test2'], + program.testNames) + + def test_NonExit(self): + program = unittest.main(exit=False, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + testLoader=self.FooBarLoader()) + self.assertTrue(hasattr(program, 'result')) + + + def test_Exit(self): + self.assertRaises( + SystemExit, + unittest.main, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + exit=True, + testLoader=self.FooBarLoader()) + + + def test_ExitAsDefault(self): + self.assertRaises( + SystemExit, + unittest.main, + argv=["foobar"], + testRunner=unittest.TextTestRunner(stream=io.StringIO()), + testLoader=self.FooBarLoader()) + + +class InitialisableProgram(unittest.TestProgram): + exit = False + result = None + verbosity = 1 + defaultTest = None + tb_locals = False + testRunner = None + testLoader = unittest.defaultTestLoader + module = '__main__' + progName = 'test' + test = 'test' + def __init__(self, *args): + pass + +RESULT = object() + +class FakeRunner(object): + initArgs = None + test = None + raiseError = 0 + + def __init__(self, **kwargs): + FakeRunner.initArgs = kwargs + if FakeRunner.raiseError: + FakeRunner.raiseError -= 1 + raise TypeError + + def run(self, test): + FakeRunner.test = test + return RESULT + + +class TestCommandLineArgs(unittest.TestCase): + + def setUp(self): + self.program = InitialisableProgram() + self.program.createTests = lambda: None + FakeRunner.initArgs = None + FakeRunner.test = None + FakeRunner.raiseError = 0 + + def testVerbosity(self): + program = self.program + + for opt in '-q', '--quiet': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 0) + + for opt in '-v', '--verbose': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 2) + + def testBufferCatchFailfast(self): + program = self.program + for arg, attr in (('buffer', 'buffer'), ('failfast', 'failfast'), + ('catch', 'catchbreak')): + + setattr(program, attr, None) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), False) + + false = [] + setattr(program, attr, false) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), false) + + true = [42] + setattr(program, attr, true) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), true) + + short_opt = '-%s' % arg[0] + long_opt = '--%s' % arg + for opt in short_opt, long_opt: + setattr(program, attr, None) + program.parseArgs([None, opt]) + self.assertIs(getattr(program, attr), True) + + setattr(program, attr, False) + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: + program.parseArgs([None, opt]) + self.assertEqual(cm.exception.args, (2,)) + + setattr(program, attr, True) + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: + program.parseArgs([None, opt]) + self.assertEqual(cm.exception.args, (2,)) + + def testWarning(self): + """Test the warnings argument""" + # see #10535 + class FakeTP(unittest.TestProgram): + def parseArgs(self, *args, **kw): pass + def runTests(self, *args, **kw): pass + warnoptions = sys.warnoptions[:] + try: + sys.warnoptions[:] = [] + # no warn options, no arg -> default + self.assertEqual(FakeTP().warnings, 'default') + # no warn options, w/ arg -> arg value + self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore') + sys.warnoptions[:] = ['somevalue'] + # warn options, no arg -> None + # warn options, w/ arg -> arg value + self.assertEqual(FakeTP().warnings, None) + self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore') + finally: + sys.warnoptions[:] = warnoptions + + def testRunTestsRunnerClass(self): + program = self.program + + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + program.warnings = 'warnings' + + program.runTests() + + self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity', + 'failfast': 'failfast', + 'buffer': 'buffer', + 'tb_locals': False, + 'warnings': 'warnings'}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testRunTestsRunnerInstance(self): + program = self.program + + program.testRunner = FakeRunner() + FakeRunner.initArgs = None + + program.runTests() + + # A new FakeRunner should not have been instantiated + self.assertIsNone(FakeRunner.initArgs) + + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def test_locals(self): + program = self.program + + program.testRunner = FakeRunner + program.parseArgs([None, '--locals']) + self.assertEqual(True, program.tb_locals) + program.runTests() + self.assertEqual(FakeRunner.initArgs, {'buffer': False, + 'failfast': False, + 'tb_locals': True, + 'verbosity': 1, + 'warnings': None}) + + def testRunTestsOldRunnerClass(self): + program = self.program + + # Two TypeErrors are needed to fall all the way back to old-style + # runners - one to fail tb_locals, one to fail buffer etc. + FakeRunner.raiseError = 2 + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + program.test = 'test' + + program.runTests() + + # If initialising raises a type error it should be retried + # without the new keyword arguments + self.assertEqual(FakeRunner.initArgs, {}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testCatchBreakInstallsHandler(self): + module = sys.modules['unittest.main'] + original = module.installHandler + def restore(): + module.installHandler = original + self.addCleanup(restore) + + self.installed = False + def fakeInstallHandler(): + self.installed = True + module.installHandler = fakeInstallHandler + + program = self.program + program.catchbreak = True + + program.testRunner = FakeRunner + + program.runTests() + self.assertTrue(self.installed) + + def _patch_isfile(self, names, exists=True): + def isfile(path): + return path in names + original = os.path.isfile + os.path.isfile = isfile + def restore(): + os.path.isfile = original + self.addCleanup(restore) + + + def testParseArgsFileNames(self): + # running tests with filenames instead of module names + program = self.program + argv = ['progname', 'foo.py', 'bar.Py', 'baz.PY', 'wing.txt'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + # note that 'wing.txt' is not a Python file so the name should + # *not* be converted to a module name + expected = ['foo', 'bar', 'baz', 'wing.txt'] + self.assertEqual(program.testNames, expected) + + + def testParseArgsFilePaths(self): + program = self.program + argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + expected = ['foo.bar.baz', 'green.red'] + self.assertEqual(program.testNames, expected) + + + def testParseArgsNonExistentFiles(self): + program = self.program + argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] + self._patch_isfile([]) + + program.createTests = lambda: None + program.parseArgs(argv) + + self.assertEqual(program.testNames, argv[1:]) + + def testParseArgsAbsolutePathsThatCanBeConverted(self): + cur_dir = os.getcwd() + program = self.program + def _join(name): + return os.path.join(cur_dir, name) + argv = ['progname', _join('foo/bar/baz.py'), _join('green\\red.py')] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + expected = ['foo.bar.baz', 'green.red'] + self.assertEqual(program.testNames, expected) + + def testParseArgsAbsolutePathsThatCannotBeConverted(self): + program = self.program + # even on Windows '/...' is considered absolute by os.path.abspath + argv = ['progname', '/foo/bar/baz.py', '/green/red.py'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + self.assertEqual(program.testNames, argv[1:]) + + # it may be better to use platform specific functions to normalise paths + # rather than accepting '.PY' and '\' as file separator on Linux / Mac + # it would also be better to check that a filename is a valid module + # identifier (we have a regex for this in loader.py) + # for invalid filenames should we raise a useful error rather than + # leaving the current error message (import of filename fails) in place? + + def testParseArgsSelectedTestNames(self): + program = self.program + argv = ['progname', '-k', 'foo', '-k', 'bar', '-k', '*pat*'] + + program.createTests = lambda: None + program.parseArgs(argv) + + self.assertEqual(program.testNamePatterns, ['*foo*', '*bar*', '*pat*']) + + def testSelectedTestNamesFunctionalTest(self): + def run_unittest(args): + p = subprocess.Popen([sys.executable, '-m', 'unittest'] + args, + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, cwd=os.path.dirname(__file__)) + with p: + _, stderr = p.communicate() + return stderr.decode() + + t = '_test_warnings' + self.assertIn('Ran 7 tests', run_unittest([t])) + self.assertIn('Ran 7 tests', run_unittest(['-k', 'TestWarnings', t])) + self.assertIn('Ran 7 tests', run_unittest(['discover', '-p', '*_test*', '-k', 'TestWarnings'])) + self.assertIn('Ran 2 tests', run_unittest(['-k', 'f', t])) + self.assertIn('Ran 7 tests', run_unittest(['-k', 't', t])) + self.assertIn('Ran 3 tests', run_unittest(['-k', '*t', t])) + self.assertIn('Ran 7 tests', run_unittest(['-k', '*test_warnings.*Warning*', t])) + self.assertIn('Ran 1 test', run_unittest(['-k', '*test_warnings.*warning*', t])) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_result.py b/Monika After Story/game/python-packages/unittest/test/test_result.py new file mode 100644 index 0000000000..0ffb87b402 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_result.py @@ -0,0 +1,704 @@ +import io +import sys +import textwrap + +from test import support + +import traceback +import unittest + + +class MockTraceback(object): + class TracebackException: + def __init__(self, *args, **kwargs): + self.capture_locals = kwargs.get('capture_locals', False) + def format(self): + result = ['A traceback'] + if self.capture_locals: + result.append('locals') + return result + +def restore_traceback(): + unittest.result.traceback = traceback + + +class Test_TestResult(unittest.TestCase): + # Note: there are not separate tests for TestResult.wasSuccessful(), + # TestResult.errors, TestResult.failures, TestResult.testsRun or + # TestResult.shouldStop because these only have meaning in terms of + # other TestResult methods. + # + # Accordingly, tests for the aforenamed attributes are incorporated + # in with the tests for the defining methods. + ################################################################ + + def test_init(self): + result = unittest.TestResult() + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 0) + self.assertEqual(result.shouldStop, False) + self.assertIsNone(result._stdout_buffer) + self.assertIsNone(result._stderr_buffer) + + # "This method can be called to signal that the set of tests being + # run should be aborted by setting the TestResult's shouldStop + # attribute to True." + def test_stop(self): + result = unittest.TestResult() + + result.stop() + + self.assertEqual(result.shouldStop, True) + + # "Called when the test case test is about to be run. The default + # implementation simply increments the instance's testsRun counter." + def test_startTest(self): + class Foo(unittest.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # "Called after the test case test has been executed, regardless of + # the outcome. The default implementation does nothing." + def test_stopTest(self): + class Foo(unittest.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # Same tests as above; make sure nothing has changed + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "Called before and after tests are run. The default implementation does nothing." + def test_startTestRun_stopTestRun(self): + result = unittest.TestResult() + result.startTestRun() + result.stopTestRun() + + # "addSuccess(test)" + # ... + # "Called when the test case test succeeds" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addSuccess(self): + class Foo(unittest.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest.TestResult() + + result.startTest(test) + result.addSuccess(test) + result.stopTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "addFailure(test, err)" + # ... + # "Called when the test case test signals a failure. err is a tuple of + # the form returned by sys.exc_info(): (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addFailure(self): + class Foo(unittest.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + test.fail("foo") + except: + exc_info_tuple = sys.exc_info() + + result = unittest.TestResult() + + result.startTest(test) + result.addFailure(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.failures[0] + self.assertIs(test_case, test) + self.assertIsInstance(formatted_exc, str) + + # "addError(test, err)" + # ... + # "Called when the test case test raises an unexpected exception err + # is a tuple of the form returned by sys.exc_info(): + # (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addError(self): + class Foo(unittest.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + raise TypeError() + except: + exc_info_tuple = sys.exc_info() + + result = unittest.TestResult() + + result.startTest(test) + result.addError(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.errors[0] + self.assertIs(test_case, test) + self.assertIsInstance(formatted_exc, str) + + def test_addError_locals(self): + class Foo(unittest.TestCase): + def test_1(self): + 1/0 + + test = Foo('test_1') + result = unittest.TestResult() + result.tb_locals = True + + unittest.result.traceback = MockTraceback + self.addCleanup(restore_traceback) + result.startTestRun() + test.run(result) + result.stopTestRun() + + self.assertEqual(len(result.errors), 1) + test_case, formatted_exc = result.errors[0] + self.assertEqual('A tracebacklocals', formatted_exc) + + def test_addSubTest(self): + class Foo(unittest.TestCase): + def test_1(self): + nonlocal subtest + with self.subTest(foo=1): + subtest = self._subtest + try: + 1/0 + except ZeroDivisionError: + exc_info_tuple = sys.exc_info() + # Register an error by hand (to check the API) + result.addSubTest(test, subtest, exc_info_tuple) + # Now trigger a failure + self.fail("some recognizable failure") + + subtest = None + test = Foo('test_1') + result = unittest.TestResult() + + test.run(result) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.errors[0] + self.assertIs(test_case, subtest) + self.assertIn("ZeroDivisionError", formatted_exc) + test_case, formatted_exc = result.failures[0] + self.assertIs(test_case, subtest) + self.assertIn("some recognizable failure", formatted_exc) + + def testGetDescriptionWithoutDocstring(self): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + 'testGetDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult)') + + def testGetSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1, bar=2): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult) (foo=1, bar=2)') + with self.subTest('some message'): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult) [some message]') + + def testGetSubTestDescriptionWithoutDocstringAndParams(self): + with self.subTest(): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstringAndParams ' + '(' + __name__ + '.Test_TestResult) ()') + + def testGetSubTestDescriptionForFalsyValues(self): + expected = 'testGetSubTestDescriptionForFalsyValues (%s.Test_TestResult) [%s]' + result = unittest.TextTestResult(None, True, 1) + for arg in [0, None, []]: + with self.subTest(arg): + self.assertEqual( + result.getDescription(self._subtest), + expected % (__name__, arg) + ) + + def testGetNestedSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1): + with self.subTest(baz=2, bar=3): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetNestedSubTestDescriptionWithoutDocstring ' + '(' + __name__ + '.Test_TestResult) (baz=2, bar=3, foo=1)') + + def testGetDuplicatedNestedSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1, bar=2): + with self.subTest(baz=3, bar=4): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetDuplicatedNestedSubTestDescriptionWithoutDocstring ' + '(' + __name__ + '.Test_TestResult) (baz=3, bar=4, foo=1)') + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testGetDescriptionWithOneLineDocstring(self): + """Tests getDescription() for a method with a docstring.""" + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a docstring.')) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testGetSubTestDescriptionWithOneLineDocstring(self): + """Tests getDescription() for a method with a docstring.""" + result = unittest.TextTestResult(None, True, 1) + with self.subTest(foo=1, bar=2): + self.assertEqual( + result.getDescription(self._subtest), + ('testGetSubTestDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' + 'Tests getDescription() for a method with a docstring.')) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testGetDescriptionWithMultiLineDocstring(self): + """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. + """ + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a longer ' + 'docstring.')) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testGetSubTestDescriptionWithMultiLineDocstring(self): + """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. + """ + result = unittest.TextTestResult(None, True, 1) + with self.subTest(foo=1, bar=2): + self.assertEqual( + result.getDescription(self._subtest), + ('testGetSubTestDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' + 'Tests getDescription() for a method with a longer ' + 'docstring.')) + + def testStackFrameTrimming(self): + class Frame(object): + class tb_frame(object): + f_globals = {} + result = unittest.TestResult() + self.assertFalse(result._is_relevant_tb_level(Frame)) + + Frame.tb_frame.f_globals['__unittest'] = True + self.assertTrue(result._is_relevant_tb_level(Frame)) + + def testFailFast(self): + result = unittest.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addError(None, None) + self.assertTrue(result.shouldStop) + + result = unittest.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addFailure(None, None) + self.assertTrue(result.shouldStop) + + result = unittest.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addUnexpectedSuccess(None) + self.assertTrue(result.shouldStop) + + def testFailFastSetByRunner(self): + runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True) + def test(result): + self.assertTrue(result.failfast) + result = runner.run(test) + + +classDict = dict(unittest.TestResult.__dict__) +for m in ('addSkip', 'addExpectedFailure', 'addUnexpectedSuccess', + '__init__'): + del classDict[m] + +def __init__(self, stream=None, descriptions=None, verbosity=None): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = False + self.buffer = False + self.tb_locals = False + +classDict['__init__'] = __init__ +OldResult = type('OldResult', (object,), classDict) + +class Test_OldTestResult(unittest.TestCase): + + def assertOldResultWarning(self, test, failures): + with support.check_warnings(("TestResult has no add.+ method,", + RuntimeWarning)): + result = OldResult() + test.run(result) + self.assertEqual(len(result.failures), failures) + + def testOldTestResult(self): + class Test(unittest.TestCase): + def testSkip(self): + self.skipTest('foobar') + @unittest.expectedFailure + def testExpectedFail(self): + raise TypeError + @unittest.expectedFailure + def testUnexpectedSuccess(self): + pass + + for test_name, should_pass in (('testSkip', True), + ('testExpectedFail', True), + ('testUnexpectedSuccess', False)): + test = Test(test_name) + self.assertOldResultWarning(test, int(not should_pass)) + + def testOldTestTesultSetup(self): + class Test(unittest.TestCase): + def setUp(self): + self.skipTest('no reason') + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def testOldTestResultClass(self): + @unittest.skip('no reason') + class Test(unittest.TestCase): + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def testOldResultWithRunner(self): + class Test(unittest.TestCase): + def testFoo(self): + pass + runner = unittest.TextTestRunner(resultclass=OldResult, + stream=io.StringIO()) + # This will raise an exception if TextTestRunner can't handle old + # test result objects + runner.run(Test('testFoo')) + + +class TestOutputBuffering(unittest.TestCase): + + def setUp(self): + self._real_out = sys.stdout + self._real_err = sys.stderr + + def tearDown(self): + sys.stdout = self._real_out + sys.stderr = self._real_err + + def testBufferOutputOff(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest.TestResult() + self.assertFalse(result.buffer) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + def testBufferOutputStartTestAddSuccess(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest.TestResult() + self.assertFalse(result.buffer) + + result.buffer = True + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIsNot(real_out, sys.stdout) + self.assertIsNot(real_err, sys.stderr) + self.assertIsInstance(sys.stdout, io.StringIO) + self.assertIsInstance(sys.stderr, io.StringIO) + self.assertIsNot(sys.stdout, sys.stderr) + + out_stream = sys.stdout + err_stream = sys.stderr + + result._original_stdout = io.StringIO() + result._original_stderr = io.StringIO() + + print('foo') + print('bar', file=sys.stderr) + + self.assertEqual(out_stream.getvalue(), 'foo\n') + self.assertEqual(err_stream.getvalue(), 'bar\n') + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + result.addSuccess(self) + result.stopTest(self) + + self.assertIs(sys.stdout, result._original_stdout) + self.assertIs(sys.stderr, result._original_stderr) + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + self.assertEqual(out_stream.getvalue(), '') + self.assertEqual(err_stream.getvalue(), '') + + + def getStartedResult(self): + result = unittest.TestResult() + result.buffer = True + result.startTest(self) + return result + + def testBufferOutputAddErrorOrFailure(self): + unittest.result.traceback = MockTraceback + self.addCleanup(restore_traceback) + + for message_attr, add_attr, include_error in [ + ('errors', 'addError', True), + ('failures', 'addFailure', False), + ('errors', 'addError', True), + ('failures', 'addFailure', False) + ]: + result = self.getStartedResult() + buffered_out = sys.stdout + buffered_err = sys.stderr + result._original_stdout = io.StringIO() + result._original_stderr = io.StringIO() + + print('foo', file=sys.stdout) + if include_error: + print('bar', file=sys.stderr) + + + addFunction = getattr(result, add_attr) + addFunction(self, (None, None, None)) + result.stopTest(self) + + result_list = getattr(result, message_attr) + self.assertEqual(len(result_list), 1) + + test, message = result_list[0] + expectedOutMessage = textwrap.dedent(""" + Stdout: + foo + """) + expectedErrMessage = '' + if include_error: + expectedErrMessage = textwrap.dedent(""" + Stderr: + bar + """) + + expectedFullMessage = 'A traceback%s%s' % (expectedOutMessage, expectedErrMessage) + + self.assertIs(test, self) + self.assertEqual(result._original_stdout.getvalue(), expectedOutMessage) + self.assertEqual(result._original_stderr.getvalue(), expectedErrMessage) + self.assertMultiLineEqual(message, expectedFullMessage) + + def testBufferSetupClass(self): + result = unittest.TestResult() + result.buffer = True + + class Foo(unittest.TestCase): + @classmethod + def setUpClass(cls): + 1/0 + def test_foo(self): + pass + suite = unittest.TestSuite([Foo('test_foo')]) + suite(result) + self.assertEqual(len(result.errors), 1) + + def testBufferTearDownClass(self): + result = unittest.TestResult() + result.buffer = True + + class Foo(unittest.TestCase): + @classmethod + def tearDownClass(cls): + 1/0 + def test_foo(self): + pass + suite = unittest.TestSuite([Foo('test_foo')]) + suite(result) + self.assertEqual(len(result.errors), 1) + + def testBufferSetUpModule(self): + result = unittest.TestResult() + result.buffer = True + + class Foo(unittest.TestCase): + def test_foo(self): + pass + class Module(object): + @staticmethod + def setUpModule(): + 1/0 + + Foo.__module__ = 'Module' + sys.modules['Module'] = Module + self.addCleanup(sys.modules.pop, 'Module') + suite = unittest.TestSuite([Foo('test_foo')]) + suite(result) + self.assertEqual(len(result.errors), 1) + + def testBufferTearDownModule(self): + result = unittest.TestResult() + result.buffer = True + + class Foo(unittest.TestCase): + def test_foo(self): + pass + class Module(object): + @staticmethod + def tearDownModule(): + 1/0 + + Foo.__module__ = 'Module' + sys.modules['Module'] = Module + self.addCleanup(sys.modules.pop, 'Module') + suite = unittest.TestSuite([Foo('test_foo')]) + suite(result) + self.assertEqual(len(result.errors), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_runner.py b/Monika After Story/game/python-packages/unittest/test/test_runner.py new file mode 100644 index 0000000000..dd9a1b6d9a --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_runner.py @@ -0,0 +1,1013 @@ +import io +import os +import sys +import pickle +import subprocess + +import unittest +from unittest.case import _Outcome + +from unittest.test.support import (LoggingResult, + ResultWithNoStartTestRunStopTestRun) + + +def resultFactory(*_): + return unittest.TestResult() + + +def getRunner(): + return unittest.TextTestRunner(resultclass=resultFactory, + stream=io.StringIO()) + + +def runTests(*cases): + suite = unittest.TestSuite() + for case in cases: + tests = unittest.defaultTestLoader.loadTestsFromTestCase(case) + suite.addTests(tests) + + runner = getRunner() + + # creating a nested suite exposes some potential bugs + realSuite = unittest.TestSuite() + realSuite.addTest(suite) + # adding empty suites to the end exposes potential bugs + suite.addTest(unittest.TestSuite()) + realSuite.addTest(unittest.TestSuite()) + return runner.run(realSuite) + + +def cleanup(ordering, blowUp=False): + if not blowUp: + ordering.append('cleanup_good') + else: + ordering.append('cleanup_exc') + raise Exception('CleanUpExc') + + +class TestCleanUp(unittest.TestCase): + def testCleanUp(self): + class TestableTest(unittest.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + self.assertEqual(test._cleanups, []) + + cleanups = [] + + def cleanup1(*args, **kwargs): + cleanups.append((1, args, kwargs)) + + def cleanup2(*args, **kwargs): + cleanups.append((2, args, kwargs)) + + test.addCleanup(cleanup1, 1, 2, 3, four='hello', five='goodbye') + test.addCleanup(cleanup2) + + self.assertEqual(test._cleanups, + [(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')), + (cleanup2, (), {})]) + + self.assertTrue(test.doCleanups()) + self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))]) + + def testCleanUpWithErrors(self): + class TestableTest(unittest.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + outcome = test._outcome = _Outcome() + + CleanUpExc = Exception('foo') + exc2 = Exception('bar') + def cleanup1(): + raise CleanUpExc + + def cleanup2(): + raise exc2 + + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + self.assertFalse(test.doCleanups()) + self.assertFalse(outcome.success) + + ((_, (Type1, instance1, _)), + (_, (Type2, instance2, _))) = reversed(outcome.errors) + self.assertEqual((Type1, instance1), (Exception, CleanUpExc)) + self.assertEqual((Type2, instance2), (Exception, exc2)) + + def testCleanupInRun(self): + blowUp = False + ordering = [] + + class TestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp') + if blowUp: + raise Exception('foo') + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + def cleanup2(): + ordering.append('cleanup2') + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + def success(some_test): + self.assertEqual(some_test, test) + ordering.append('success') + + result = unittest.TestResult() + result.addSuccess = success + + test.run(result) + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', + 'cleanup2', 'cleanup1', 'success']) + + blowUp = True + ordering = [] + test = TestableTest('testNothing') + test.addCleanup(cleanup1) + test.run(result) + self.assertEqual(ordering, ['setUp', 'cleanup1']) + + def testTestCaseDebugExecutesCleanups(self): + ordering = [] + + class TestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup1) + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + test.addCleanup(cleanup2) + def cleanup2(): + ordering.append('cleanup2') + + test.debug() + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', 'cleanup1', 'cleanup2']) + + +class TestClassCleanup(unittest.TestCase): + def test_addClassCleanUp(self): + class TestableTest(unittest.TestCase): + def testNothing(self): + pass + test = TestableTest('testNothing') + self.assertEqual(test._class_cleanups, []) + class_cleanups = [] + + def class_cleanup1(*args, **kwargs): + class_cleanups.append((3, args, kwargs)) + + def class_cleanup2(*args, **kwargs): + class_cleanups.append((4, args, kwargs)) + + TestableTest.addClassCleanup(class_cleanup1, 1, 2, 3, + four='hello', five='goodbye') + TestableTest.addClassCleanup(class_cleanup2) + + self.assertEqual(test._class_cleanups, + [(class_cleanup1, (1, 2, 3), + dict(four='hello', five='goodbye')), + (class_cleanup2, (), {})]) + + TestableTest.doClassCleanups() + self.assertEqual(class_cleanups, [(4, (), {}), (3, (1, 2, 3), + dict(four='hello', five='goodbye'))]) + + def test_run_class_cleanUp(self): + ordering = [] + blowUp = True + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering) + if blowUp: + raise Exception() + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + runTests(TestableTest) + self.assertEqual(ordering, ['setUpClass', 'cleanup_good']) + + ordering = [] + blowUp = False + runTests(TestableTest) + self.assertEqual(ordering, + ['setUpClass', 'test', 'tearDownClass', 'cleanup_good']) + + def test_debug_executes_classCleanUp(self): + ordering = [] + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering) + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest) + suite.debug() + self.assertEqual(ordering, + ['setUpClass', 'test', 'tearDownClass', 'cleanup_good']) + + def test_doClassCleanups_with_errors_addClassCleanUp(self): + class TestableTest(unittest.TestCase): + def testNothing(self): + pass + + def cleanup1(): + raise Exception('cleanup1') + + def cleanup2(): + raise Exception('cleanup2') + + TestableTest.addClassCleanup(cleanup1) + TestableTest.addClassCleanup(cleanup2) + with self.assertRaises(Exception) as e: + TestableTest.doClassCleanups() + self.assertEqual(e, 'cleanup1') + + def test_with_errors_addCleanUp(self): + ordering = [] + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering) + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup, ordering, blowUp=True) + def testNothing(self): + pass + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpClass', 'setUp', 'cleanup_exc', + 'tearDownClass', 'cleanup_good']) + + def test_run_with_errors_addClassCleanUp(self): + ordering = [] + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering, blowUp=True) + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup, ordering) + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpClass', 'setUp', 'test', 'cleanup_good', + 'tearDownClass', 'cleanup_exc']) + + def test_with_errors_in_addClassCleanup_and_setUps(self): + ordering = [] + class_blow_up = False + method_blow_up = False + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering, blowUp=True) + if class_blow_up: + raise Exception('ClassExc') + def setUp(self): + ordering.append('setUp') + if method_blow_up: + raise Exception('MethodExc') + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpClass', 'setUp', 'test', + 'tearDownClass', 'cleanup_exc']) + ordering = [] + class_blow_up = True + method_blow_up = False + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: ClassExc') + self.assertEqual(result.errors[1][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpClass', 'cleanup_exc']) + + ordering = [] + class_blow_up = False + method_blow_up = True + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: MethodExc') + self.assertEqual(result.errors[1][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpClass', 'setUp', 'tearDownClass', + 'cleanup_exc']) + + +class TestModuleCleanUp(unittest.TestCase): + def test_add_and_do_ModuleCleanup(self): + module_cleanups = [] + + def module_cleanup1(*args, **kwargs): + module_cleanups.append((3, args, kwargs)) + + def module_cleanup2(*args, **kwargs): + module_cleanups.append((4, args, kwargs)) + + class Module(object): + unittest.addModuleCleanup(module_cleanup1, 1, 2, 3, + four='hello', five='goodbye') + unittest.addModuleCleanup(module_cleanup2) + + self.assertEqual(unittest.case._module_cleanups, + [(module_cleanup1, (1, 2, 3), + dict(four='hello', five='goodbye')), + (module_cleanup2, (), {})]) + + unittest.case.doModuleCleanups() + self.assertEqual(module_cleanups, [(4, (), {}), (3, (1, 2, 3), + dict(four='hello', five='goodbye'))]) + self.assertEqual(unittest.case._module_cleanups, []) + + def test_doModuleCleanup_with_errors_in_addModuleCleanup(self): + module_cleanups = [] + + def module_cleanup_good(*args, **kwargs): + module_cleanups.append((3, args, kwargs)) + + def module_cleanup_bad(*args, **kwargs): + raise Exception('CleanUpExc') + + class Module(object): + unittest.addModuleCleanup(module_cleanup_good, 1, 2, 3, + four='hello', five='goodbye') + unittest.addModuleCleanup(module_cleanup_bad) + self.assertEqual(unittest.case._module_cleanups, + [(module_cleanup_good, (1, 2, 3), + dict(four='hello', five='goodbye')), + (module_cleanup_bad, (), {})]) + with self.assertRaises(Exception) as e: + unittest.case.doModuleCleanups() + self.assertEqual(str(e.exception), 'CleanUpExc') + self.assertEqual(unittest.case._module_cleanups, []) + + def test_addModuleCleanup_arg_errors(self): + cleanups = [] + def cleanup(*args, **kwargs): + cleanups.append((args, kwargs)) + + class Module(object): + unittest.addModuleCleanup(cleanup, 1, 2, function='hello') + with self.assertRaises(TypeError): + unittest.addModuleCleanup(function=cleanup, arg='hello') + with self.assertRaises(TypeError): + unittest.addModuleCleanup() + unittest.case.doModuleCleanups() + self.assertEqual(cleanups, + [((1, 2), {'function': 'hello'})]) + + def test_run_module_cleanUp(self): + blowUp = True + ordering = [] + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering) + if blowUp: + raise Exception('setUpModule Exc') + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + TestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + result = runTests(TestableTest) + self.assertEqual(ordering, ['setUpModule', 'cleanup_good']) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: setUpModule Exc') + + ordering = [] + blowUp = False + runTests(TestableTest) + self.assertEqual(ordering, + ['setUpModule', 'setUpClass', 'test', 'tearDownClass', + 'tearDownModule', 'cleanup_good']) + self.assertEqual(unittest.case._module_cleanups, []) + + def test_run_multiple_module_cleanUp(self): + blowUp = True + blowUp2 = False + ordering = [] + class Module1(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering) + if blowUp: + raise Exception() + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class Module2(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule2') + unittest.addModuleCleanup(cleanup, ordering) + if blowUp2: + raise Exception() + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule2') + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + class TestableTest2(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass2') + def testNothing(self): + ordering.append('test2') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass2') + + TestableTest.__module__ = 'Module1' + sys.modules['Module1'] = Module1 + TestableTest2.__module__ = 'Module2' + sys.modules['Module2'] = Module2 + runTests(TestableTest, TestableTest2) + self.assertEqual(ordering, ['setUpModule', 'cleanup_good', + 'setUpModule2', 'setUpClass2', 'test2', + 'tearDownClass2', 'tearDownModule2', + 'cleanup_good']) + ordering = [] + blowUp = False + blowUp2 = True + runTests(TestableTest, TestableTest2) + self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'test', + 'tearDownClass', 'tearDownModule', + 'cleanup_good', 'setUpModule2', + 'cleanup_good']) + + ordering = [] + blowUp = False + blowUp2 = False + runTests(TestableTest, TestableTest2) + self.assertEqual(ordering, + ['setUpModule', 'setUpClass', 'test', 'tearDownClass', + 'tearDownModule', 'cleanup_good', 'setUpModule2', + 'setUpClass2', 'test2', 'tearDownClass2', + 'tearDownModule2', 'cleanup_good']) + self.assertEqual(unittest.case._module_cleanups, []) + + def test_debug_module_executes_cleanUp(self): + ordering = [] + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering) + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + TestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest) + suite.debug() + self.assertEqual(ordering, + ['setUpModule', 'setUpClass', 'test', 'tearDownClass', + 'tearDownModule', 'cleanup_good']) + self.assertEqual(unittest.case._module_cleanups, []) + + def test_addClassCleanup_arg_errors(self): + cleanups = [] + def cleanup(*args, **kwargs): + cleanups.append((args, kwargs)) + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.addClassCleanup(cleanup, 1, 2, function=3, cls=4) + with self.assertRaises(TypeError): + cls.addClassCleanup(function=cleanup, arg='hello') + def testNothing(self): + pass + + with self.assertRaises(TypeError): + TestableTest.addClassCleanup() + with self.assertRaises(TypeError): + unittest.TestCase.addCleanup(cls=TestableTest(), function=cleanup) + runTests(TestableTest) + self.assertEqual(cleanups, + [((1, 2), {'function': 3, 'cls': 4})]) + + def test_addCleanup_arg_errors(self): + cleanups = [] + def cleanup(*args, **kwargs): + cleanups.append((args, kwargs)) + + class TestableTest(unittest.TestCase): + def setUp(self2): + self2.addCleanup(cleanup, 1, 2, function=3, self=4) + with self.assertRaises(TypeError): + self2.addCleanup(function=cleanup, arg='hello') + def testNothing(self): + pass + + with self.assertRaises(TypeError): + TestableTest().addCleanup() + with self.assertRaises(TypeError): + unittest.TestCase.addCleanup(self=TestableTest(), function=cleanup) + runTests(TestableTest) + self.assertEqual(cleanups, + [((1, 2), {'function': 3, 'self': 4})]) + + def test_with_errors_in_addClassCleanup(self): + ordering = [] + + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering) + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + cls.addClassCleanup(cleanup, ordering, blowUp=True) + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + TestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpModule', 'setUpClass', 'test', 'tearDownClass', + 'cleanup_exc', 'tearDownModule', 'cleanup_good']) + + def test_with_errors_in_addCleanup(self): + ordering = [] + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering) + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup, ordering, blowUp=True) + def testNothing(self): + ordering.append('test') + def tearDown(self): + ordering.append('tearDown') + + TestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpModule', 'setUp', 'test', 'tearDown', + 'cleanup_exc', 'tearDownModule', 'cleanup_good']) + + def test_with_errors_in_addModuleCleanup_and_setUps(self): + ordering = [] + module_blow_up = False + class_blow_up = False + method_blow_up = False + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup, ordering, blowUp=True) + if module_blow_up: + raise Exception('ModuleExc') + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + if class_blow_up: + raise Exception('ClassExc') + def setUp(self): + ordering.append('setUp') + if method_blow_up: + raise Exception('MethodExc') + def testNothing(self): + ordering.append('test') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + + TestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, + ['setUpModule', 'setUpClass', 'setUp', 'test', + 'tearDownClass', 'tearDownModule', + 'cleanup_exc']) + + ordering = [] + module_blow_up = True + class_blow_up = False + method_blow_up = False + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(result.errors[1][1].splitlines()[-1], + 'Exception: ModuleExc') + self.assertEqual(ordering, ['setUpModule', 'cleanup_exc']) + + ordering = [] + module_blow_up = False + class_blow_up = True + method_blow_up = False + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: ClassExc') + self.assertEqual(result.errors[1][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, ['setUpModule', 'setUpClass', + 'tearDownModule', 'cleanup_exc']) + + ordering = [] + module_blow_up = False + class_blow_up = False + method_blow_up = True + result = runTests(TestableTest) + self.assertEqual(result.errors[0][1].splitlines()[-1], + 'Exception: MethodExc') + self.assertEqual(result.errors[1][1].splitlines()[-1], + 'Exception: CleanUpExc') + self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'setUp', + 'tearDownClass', 'tearDownModule', + 'cleanup_exc']) + + def test_module_cleanUp_with_multiple_classes(self): + ordering =[] + def cleanup1(): + ordering.append('cleanup1') + + def cleanup2(): + ordering.append('cleanup2') + + def cleanup3(): + ordering.append('cleanup3') + + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + unittest.addModuleCleanup(cleanup1) + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class TestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup2) + def testNothing(self): + ordering.append('test') + def tearDown(self): + ordering.append('tearDown') + + class OtherTestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp2') + self.addCleanup(cleanup3) + def testNothing(self): + ordering.append('test2') + def tearDown(self): + ordering.append('tearDown2') + + TestableTest.__module__ = 'Module' + OtherTestableTest.__module__ = 'Module' + sys.modules['Module'] = Module + runTests(TestableTest, OtherTestableTest) + self.assertEqual(ordering, + ['setUpModule', 'setUp', 'test', 'tearDown', + 'cleanup2', 'setUp2', 'test2', 'tearDown2', + 'cleanup3', 'tearDownModule', 'cleanup1']) + + +class Test_TextTestRunner(unittest.TestCase): + """Tests for TextTestRunner.""" + + def setUp(self): + # clean the environment from pre-existing PYTHONWARNINGS to make + # test_warnings results consistent + self.pythonwarnings = os.environ.get('PYTHONWARNINGS') + if self.pythonwarnings: + del os.environ['PYTHONWARNINGS'] + + def tearDown(self): + # bring back pre-existing PYTHONWARNINGS if present + if self.pythonwarnings: + os.environ['PYTHONWARNINGS'] = self.pythonwarnings + + def test_init(self): + runner = unittest.TextTestRunner() + self.assertFalse(runner.failfast) + self.assertFalse(runner.buffer) + self.assertEqual(runner.verbosity, 1) + self.assertEqual(runner.warnings, None) + self.assertTrue(runner.descriptions) + self.assertEqual(runner.resultclass, unittest.TextTestResult) + self.assertFalse(runner.tb_locals) + + def test_multiple_inheritance(self): + class AResult(unittest.TestResult): + def __init__(self, stream, descriptions, verbosity): + super(AResult, self).__init__(stream, descriptions, verbosity) + + class ATextResult(unittest.TextTestResult, AResult): + pass + + # This used to raise an exception due to TextTestResult not passing + # on arguments in its __init__ super call + ATextResult(None, None, 1) + + def testBufferAndFailfast(self): + class Test(unittest.TestCase): + def testFoo(self): + pass + result = unittest.TestResult() + runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True, + buffer=True) + # Use our result object + runner._makeResult = lambda: result + runner.run(Test('testFoo')) + + self.assertTrue(result.failfast) + self.assertTrue(result.buffer) + + def test_locals(self): + runner = unittest.TextTestRunner(stream=io.StringIO(), tb_locals=True) + result = runner.run(unittest.TestSuite()) + self.assertEqual(True, result.tb_locals) + + def testRunnerRegistersResult(self): + class Test(unittest.TestCase): + def testFoo(self): + pass + originalRegisterResult = unittest.runner.registerResult + def cleanup(): + unittest.runner.registerResult = originalRegisterResult + self.addCleanup(cleanup) + + result = unittest.TestResult() + runner = unittest.TextTestRunner(stream=io.StringIO()) + # Use our result object + runner._makeResult = lambda: result + + self.wasRegistered = 0 + def fakeRegisterResult(thisResult): + self.wasRegistered += 1 + self.assertEqual(thisResult, result) + unittest.runner.registerResult = fakeRegisterResult + + runner.run(unittest.TestSuite()) + self.assertEqual(self.wasRegistered, 1) + + def test_works_with_result_without_startTestRun_stopTestRun(self): + class OldTextResult(ResultWithNoStartTestRunStopTestRun): + separator2 = '' + def printErrors(self): + pass + + class Runner(unittest.TextTestRunner): + def __init__(self): + super(Runner, self).__init__(io.StringIO()) + + def _makeResult(self): + return OldTextResult() + + runner = Runner() + runner.run(unittest.TestSuite()) + + def test_startTestRun_stopTestRun_called(self): + class LoggingTextResult(LoggingResult): + separator2 = '' + def printErrors(self): + pass + + class LoggingRunner(unittest.TextTestRunner): + def __init__(self, events): + super(LoggingRunner, self).__init__(io.StringIO()) + self._events = events + + def _makeResult(self): + return LoggingTextResult(self._events) + + events = [] + runner = LoggingRunner(events) + runner.run(unittest.TestSuite()) + expected = ['startTestRun', 'stopTestRun'] + self.assertEqual(events, expected) + + def test_pickle_unpickle(self): + # Issue #7197: a TextTestRunner should be (un)pickleable. This is + # required by test_multiprocessing under Windows (in verbose mode). + stream = io.StringIO("foo") + runner = unittest.TextTestRunner(stream) + for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(runner, protocol) + obj = pickle.loads(s) + # StringIO objects never compare equal, a cheap test instead. + self.assertEqual(obj.stream.getvalue(), stream.getvalue()) + + def test_resultclass(self): + def MockResultClass(*args): + return args + STREAM = object() + DESCRIPTIONS = object() + VERBOSITY = object() + runner = unittest.TextTestRunner(STREAM, DESCRIPTIONS, VERBOSITY, + resultclass=MockResultClass) + self.assertEqual(runner.resultclass, MockResultClass) + + expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY) + self.assertEqual(runner._makeResult(), expectedresult) + + def test_warnings(self): + """ + Check that warnings argument of TextTestRunner correctly affects the + behavior of the warnings. + """ + # see #10535 and the _test_warnings file for more information + + def get_parse_out_err(p): + return [b.splitlines() for b in p.communicate()] + opts = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, + cwd=os.path.dirname(__file__)) + ae_msg = b'Please use assertEqual instead.' + at_msg = b'Please use assertTrue instead.' + + # no args -> all the warnings are printed, unittest warnings only once + p = subprocess.Popen([sys.executable, '-E', '_test_warnings.py'], **opts) + with p: + out, err = get_parse_out_err(p) + self.assertIn(b'OK', err) + # check that the total number of warnings in the output is correct + self.assertEqual(len(out), 12) + # check that the numbers of the different kind of warnings is correct + for msg in [b'dw', b'iw', b'uw']: + self.assertEqual(out.count(msg), 3) + for msg in [ae_msg, at_msg, b'rw']: + self.assertEqual(out.count(msg), 1) + + args_list = ( + # passing 'ignore' as warnings arg -> no warnings + [sys.executable, '_test_warnings.py', 'ignore'], + # -W doesn't affect the result if the arg is passed + [sys.executable, '-Wa', '_test_warnings.py', 'ignore'], + # -W affects the result if the arg is not passed + [sys.executable, '-Wi', '_test_warnings.py'] + ) + # in all these cases no warnings are printed + for args in args_list: + p = subprocess.Popen(args, **opts) + with p: + out, err = get_parse_out_err(p) + self.assertIn(b'OK', err) + self.assertEqual(len(out), 0) + + + # passing 'always' as warnings arg -> all the warnings printed, + # unittest warnings only once + p = subprocess.Popen([sys.executable, '_test_warnings.py', 'always'], + **opts) + with p: + out, err = get_parse_out_err(p) + self.assertIn(b'OK', err) + self.assertEqual(len(out), 14) + for msg in [b'dw', b'iw', b'uw', b'rw']: + self.assertEqual(out.count(msg), 3) + for msg in [ae_msg, at_msg]: + self.assertEqual(out.count(msg), 1) + + def testStdErrLookedUpAtInstantiationTime(self): + # see issue 10786 + old_stderr = sys.stderr + f = io.StringIO() + sys.stderr = f + try: + runner = unittest.TextTestRunner() + self.assertTrue(runner.stream.stream is f) + finally: + sys.stderr = old_stderr + + def testSpecifiedStreamUsed(self): + # see issue 10786 + f = io.StringIO() + runner = unittest.TextTestRunner(f) + self.assertTrue(runner.stream.stream is f) + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_setups.py b/Monika After Story/game/python-packages/unittest/test/test_setups.py new file mode 100644 index 0000000000..2df703ed93 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_setups.py @@ -0,0 +1,507 @@ +import io +import sys + +import unittest + + +def resultFactory(*_): + return unittest.TestResult() + + +class TestSetups(unittest.TestCase): + + def getRunner(self): + return unittest.TextTestRunner(resultclass=resultFactory, + stream=io.StringIO()) + def runTests(self, *cases): + suite = unittest.TestSuite() + for case in cases: + tests = unittest.defaultTestLoader.loadTestsFromTestCase(case) + suite.addTests(tests) + + runner = self.getRunner() + + # creating a nested suite exposes some potential bugs + realSuite = unittest.TestSuite() + realSuite.addTest(suite) + # adding empty suites to the end exposes potential bugs + suite.addTest(unittest.TestSuite()) + realSuite.addTest(unittest.TestSuite()) + return runner.run(realSuite) + + def test_setup_class(self): + class Test(unittest.TestCase): + setUpCalled = 0 + @classmethod + def setUpClass(cls): + Test.setUpCalled += 1 + unittest.TestCase.setUpClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.setUpCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class(self): + class Test(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class_two_classes(self): + class Test(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test2.tearDownCalled += 1 + unittest.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(Test2.tearDownCalled, 1) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setupclass(self): + class BrokenTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(BrokenTest) + + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), + 'setUpClass (%s.%s)' % (__name__, BrokenTest.__qualname__)) + + def test_error_in_teardown_class(self): + class Test(unittest.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test2.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 2) + self.assertEqual(Test.tornDown, 1) + self.assertEqual(Test2.tornDown, 1) + + error, _ = result.errors[0] + self.assertEqual(str(error), + 'tearDownClass (%s.%s)' % (__name__, Test.__qualname__)) + + def test_class_not_torndown_when_setup_fails(self): + class Test(unittest.TestCase): + tornDown = False + @classmethod + def setUpClass(cls): + raise TypeError + @classmethod + def tearDownClass(cls): + Test.tornDown = True + raise TypeError('foo') + def test_one(self): + pass + + self.runTests(Test) + self.assertFalse(Test.tornDown) + + def test_class_not_setup_or_torndown_when_skipped(self): + class Test(unittest.TestCase): + classSetUp = False + tornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.tornDown = True + def test_one(self): + pass + + Test = unittest.skip("hop")(Test) + self.runTests(Test) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.tornDown) + + def test_setup_teardown_order_with_pathological_suite(self): + results = [] + + class Module1(object): + @staticmethod + def setUpModule(): + results.append('Module1.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module1.tearDownModule') + + class Module2(object): + @staticmethod + def setUpModule(): + results.append('Module2.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module2.tearDownModule') + + class Test1(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 1') + @classmethod + def tearDownClass(cls): + results.append('teardown 1') + def testOne(self): + results.append('Test1.testOne') + def testTwo(self): + results.append('Test1.testTwo') + + class Test2(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 2') + @classmethod + def tearDownClass(cls): + results.append('teardown 2') + def testOne(self): + results.append('Test2.testOne') + def testTwo(self): + results.append('Test2.testTwo') + + class Test3(unittest.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 3') + @classmethod + def tearDownClass(cls): + results.append('teardown 3') + def testOne(self): + results.append('Test3.testOne') + def testTwo(self): + results.append('Test3.testTwo') + + Test1.__module__ = Test2.__module__ = 'Module' + Test3.__module__ = 'Module2' + sys.modules['Module'] = Module1 + sys.modules['Module2'] = Module2 + + first = unittest.TestSuite((Test1('testOne'),)) + second = unittest.TestSuite((Test1('testTwo'),)) + third = unittest.TestSuite((Test2('testOne'),)) + fourth = unittest.TestSuite((Test2('testTwo'),)) + fifth = unittest.TestSuite((Test3('testOne'),)) + sixth = unittest.TestSuite((Test3('testTwo'),)) + suite = unittest.TestSuite((first, second, third, fourth, fifth, sixth)) + + runner = self.getRunner() + result = runner.run(suite) + self.assertEqual(result.testsRun, 6) + self.assertEqual(len(result.errors), 0) + + self.assertEqual(results, + ['Module1.setUpModule', 'setup 1', + 'Test1.testOne', 'Test1.testTwo', 'teardown 1', + 'setup 2', 'Test2.testOne', 'Test2.testTwo', + 'teardown 2', 'Module1.tearDownModule', + 'Module2.setUpModule', 'setup 3', + 'Test3.testOne', 'Test3.testTwo', + 'teardown 3', 'Module2.tearDownModule']) + + def test_setup_module(self): + class Module(object): + moduleSetup = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setup_module(self): + class Module(object): + moduleSetup = 0 + moduleTornDown = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + raise TypeError('foo') + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(Module.moduleTornDown, 0) + self.assertEqual(result.testsRun, 0) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'setUpModule (Module)') + + def test_testcase_with_missing_module(self): + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules.pop('Module', None) + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 2) + + def test_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + raise TypeError('foo') + + class Test(unittest.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 4) + self.assertTrue(Test.classSetUp) + self.assertTrue(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'tearDownModule (Module)') + + def test_skiptest_in_setupclass(self): + class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + raise unittest.SkipTest('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), + 'setUpClass (%s.%s)' % (__name__, Test.__qualname__)) + + def test_skiptest_in_setupmodule(self): + class Test(unittest.TestCase): + def test_one(self): + pass + def test_two(self): + pass + + class Module(object): + @staticmethod + def setUpModule(): + raise unittest.SkipTest('foo') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), 'setUpModule (Module)') + + def test_suite_debug_executes_setups_and_teardowns(self): + ordering = [] + + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + def test_something(self): + ordering.append('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) + suite.debug() + expectedOrder = ['setUpModule', 'setUpClass', 'test_something', 'tearDownClass', 'tearDownModule'] + self.assertEqual(ordering, expectedOrder) + + def test_suite_debug_propagates_exceptions(self): + class Module(object): + @staticmethod + def setUpModule(): + if phase == 0: + raise Exception('setUpModule') + @staticmethod + def tearDownModule(): + if phase == 1: + raise Exception('tearDownModule') + + class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + if phase == 2: + raise Exception('setUpClass') + @classmethod + def tearDownClass(cls): + if phase == 3: + raise Exception('tearDownClass') + def test_something(self): + if phase == 4: + raise Exception('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') + for phase, msg in enumerate(messages): + _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) + suite = unittest.TestSuite([_suite]) + with self.assertRaisesRegex(Exception, msg): + suite.debug() + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_skipping.py b/Monika After Story/game/python-packages/unittest/test/test_skipping.py new file mode 100644 index 0000000000..1c178a95f7 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_skipping.py @@ -0,0 +1,271 @@ +import unittest + +from unittest.test.support import LoggingResult + + +class Test_TestSkipping(unittest.TestCase): + + def test_skipping(self): + class Foo(unittest.TestCase): + def test_skip_me(self): + self.skipTest("skip") + events = [] + result = LoggingResult(events) + test = Foo("test_skip_me") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "skip")]) + + # Try letting setUp skip the test now. + class Foo(unittest.TestCase): + def setUp(self): + self.skipTest("testing") + def test_nothing(self): pass + events = [] + result = LoggingResult(events) + test = Foo("test_nothing") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(result.testsRun, 1) + + def test_skipping_subtests(self): + class Foo(unittest.TestCase): + def test_skip_me(self): + with self.subTest(a=1): + with self.subTest(b=2): + self.skipTest("skip 1") + self.skipTest("skip 2") + self.skipTest("skip 3") + events = [] + result = LoggingResult(events) + test = Foo("test_skip_me") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'addSkip', + 'addSkip', 'stopTest']) + self.assertEqual(len(result.skipped), 3) + subtest, msg = result.skipped[0] + self.assertEqual(msg, "skip 1") + self.assertIsInstance(subtest, unittest.TestCase) + self.assertIsNot(subtest, test) + subtest, msg = result.skipped[1] + self.assertEqual(msg, "skip 2") + self.assertIsInstance(subtest, unittest.TestCase) + self.assertIsNot(subtest, test) + self.assertEqual(result.skipped[2], (test, "skip 3")) + + def test_skipping_decorators(self): + op_table = ((unittest.skipUnless, False, True), + (unittest.skipIf, True, False)) + for deco, do_skip, dont_skip in op_table: + class Foo(unittest.TestCase): + @deco(do_skip, "testing") + def test_skip(self): pass + + @deco(dont_skip, "testing") + def test_dont_skip(self): pass + test_do_skip = Foo("test_skip") + test_dont_skip = Foo("test_dont_skip") + suite = unittest.TestSuite([test_do_skip, test_dont_skip]) + events = [] + result = LoggingResult(events) + suite.run(result) + self.assertEqual(len(result.skipped), 1) + expected = ['startTest', 'addSkip', 'stopTest', + 'startTest', 'addSuccess', 'stopTest'] + self.assertEqual(events, expected) + self.assertEqual(result.testsRun, 2) + self.assertEqual(result.skipped, [(test_do_skip, "testing")]) + self.assertTrue(result.wasSuccessful()) + + def test_skip_class(self): + @unittest.skip("testing") + class Foo(unittest.TestCase): + def test_1(self): + record.append(1) + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_skip_non_unittest_class(self): + @unittest.skip("testing") + class Mixin: + def test_1(self): + record.append(1) + class Foo(Mixin, unittest.TestCase): + pass + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_expected_failure(self): + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + self.fail("help me!") + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addExpectedFailure', 'stopTest']) + self.assertEqual(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_expected_failure_with_wrapped_class(self): + @unittest.expectedFailure + class Foo(unittest.TestCase): + def test_1(self): + self.assertTrue(False) + + events = [] + result = LoggingResult(events) + test = Foo("test_1") + test.run(result) + self.assertEqual(events, + ['startTest', 'addExpectedFailure', 'stopTest']) + self.assertEqual(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_expected_failure_with_wrapped_subclass(self): + class Foo(unittest.TestCase): + def test_1(self): + self.assertTrue(False) + + @unittest.expectedFailure + class Bar(Foo): + pass + + events = [] + result = LoggingResult(events) + test = Bar("test_1") + test.run(result) + self.assertEqual(events, + ['startTest', 'addExpectedFailure', 'stopTest']) + self.assertEqual(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_expected_failure_subtests(self): + # A failure in any subtest counts as the expected failure of the + # whole test. + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + with self.subTest(): + # This one succeeds + pass + with self.subTest(): + self.fail("help me!") + with self.subTest(): + # This one doesn't get executed + self.fail("shouldn't come here") + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addSubTestSuccess', + 'addExpectedFailure', 'stopTest']) + self.assertEqual(len(result.expectedFailures), 1) + self.assertIs(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_unexpected_success(self): + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + pass + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addUnexpectedSuccess', 'stopTest']) + self.assertFalse(result.failures) + self.assertEqual(result.unexpectedSuccesses, [test]) + self.assertFalse(result.wasSuccessful()) + + def test_unexpected_success_subtests(self): + # Success in all subtests counts as the unexpected success of + # the whole test. + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + with self.subTest(): + # This one succeeds + pass + with self.subTest(): + # So does this one + pass + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', + 'addSubTestSuccess', 'addSubTestSuccess', + 'addUnexpectedSuccess', 'stopTest']) + self.assertFalse(result.failures) + self.assertEqual(result.unexpectedSuccesses, [test]) + self.assertFalse(result.wasSuccessful()) + + def test_skip_doesnt_run_setup(self): + class Foo(unittest.TestCase): + wasSetUp = False + wasTornDown = False + def setUp(self): + Foo.wasSetUp = True + def tornDown(self): + Foo.wasTornDown = True + @unittest.skip('testing') + def test_1(self): + pass + + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertFalse(Foo.wasSetUp) + self.assertFalse(Foo.wasTornDown) + + def test_decorated_skip(self): + def decorator(func): + def inner(*a): + return func(*a) + return inner + + class Foo(unittest.TestCase): + @decorator + @unittest.skip('testing') + def test_1(self): + pass + + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + + def test_skip_without_reason(self): + class Foo(unittest.TestCase): + @unittest.skip + def test_1(self): + pass + + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "")]) + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_suite.py b/Monika After Story/game/python-packages/unittest/test/test_suite.py new file mode 100644 index 0000000000..0551a16996 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/test_suite.py @@ -0,0 +1,447 @@ +import unittest + +import gc +import sys +import weakref +from unittest.test.support import LoggingResult, TestEquality + + +### Support code for Test_TestSuite +################################################################ + +class Test(object): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + def test_3(self): pass + def runTest(self): pass + +def _mk_TestSuite(*names): + return unittest.TestSuite(Test.Foo(n) for n in names) + +################################################################ + + +class Test_TestSuite(unittest.TestCase, TestEquality): + + ### Set up attributes needed by inherited tests + ################################################################ + + # Used by TestEquality.test_eq + eq_pairs = [(unittest.TestSuite(), unittest.TestSuite()) + ,(unittest.TestSuite(), unittest.TestSuite([])) + ,(_mk_TestSuite('test_1'), _mk_TestSuite('test_1'))] + + # Used by TestEquality.test_ne + ne_pairs = [(unittest.TestSuite(), _mk_TestSuite('test_1')) + ,(unittest.TestSuite([]), _mk_TestSuite('test_1')) + ,(_mk_TestSuite('test_1', 'test_2'), _mk_TestSuite('test_1', 'test_3')) + ,(_mk_TestSuite('test_1'), _mk_TestSuite('test_2'))] + + ################################################################ + ### /Set up attributes needed by inherited tests + + ### Tests for TestSuite.__init__ + ################################################################ + + # "class TestSuite([tests])" + # + # The tests iterable should be optional + def test_init__tests_optional(self): + suite = unittest.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should deal with empty tests iterables by allowing the + # creation of an empty suite + def test_init__empty_tests(self): + suite = unittest.TestSuite([]) + + self.assertEqual(suite.countTestCases(), 0) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should allow any iterable to provide tests + def test_init__tests_from_any_iterable(self): + def tests(): + yield unittest.FunctionTestCase(lambda: None) + yield unittest.FunctionTestCase(lambda: None) + + suite_1 = unittest.TestSuite(tests()) + self.assertEqual(suite_1.countTestCases(), 2) + + suite_2 = unittest.TestSuite(suite_1) + self.assertEqual(suite_2.countTestCases(), 2) + + suite_3 = unittest.TestSuite(set(suite_1)) + self.assertEqual(suite_3.countTestCases(), 2) + + # countTestCases() still works after tests are run + suite_1.run(unittest.TestResult()) + self.assertEqual(suite_1.countTestCases(), 2) + suite_2.run(unittest.TestResult()) + self.assertEqual(suite_2.countTestCases(), 2) + suite_3.run(unittest.TestResult()) + self.assertEqual(suite_3.countTestCases(), 2) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # Does TestSuite() also allow other TestSuite() instances to be present + # in the tests iterable? + def test_init__TestSuite_instances_in_tests(self): + def tests(): + ftc = unittest.FunctionTestCase(lambda: None) + yield unittest.TestSuite([ftc]) + yield unittest.FunctionTestCase(lambda: None) + + suite = unittest.TestSuite(tests()) + self.assertEqual(suite.countTestCases(), 2) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 2) + + ################################################################ + ### /Tests for TestSuite.__init__ + + # Container types should support the iter protocol + def test_iter(self): + test1 = unittest.FunctionTestCase(lambda: None) + test2 = unittest.FunctionTestCase(lambda: None) + suite = unittest.TestSuite((test1, test2)) + + self.assertEqual(list(suite), [test1, test2]) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite returns 0? + def test_countTestCases_zero_simple(self): + suite = unittest.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite (even if it contains other empty + # TestSuite instances) returns 0? + def test_countTestCases_zero_nested(self): + class Test1(unittest.TestCase): + def test(self): + pass + + suite = unittest.TestSuite([unittest.TestSuite()]) + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + def test_countTestCases_simple(self): + test1 = unittest.FunctionTestCase(lambda: None) + test2 = unittest.FunctionTestCase(lambda: None) + suite = unittest.TestSuite((test1, test2)) + + self.assertEqual(suite.countTestCases(), 2) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 2) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Make sure this holds for nested TestSuite instances, too + def test_countTestCases_nested(self): + class Test1(unittest.TestCase): + def test1(self): pass + def test2(self): pass + + test2 = unittest.FunctionTestCase(lambda: None) + test3 = unittest.FunctionTestCase(lambda: None) + child = unittest.TestSuite((Test1('test2'), test2)) + parent = unittest.TestSuite((test3, child, Test1('test1'))) + + self.assertEqual(parent.countTestCases(), 4) + # countTestCases() still works after tests are run + parent.run(unittest.TestResult()) + self.assertEqual(parent.countTestCases(), 4) + self.assertEqual(child.countTestCases(), 2) + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + # + # And if there are no tests? What then? + def test_run__empty_suite(self): + events = [] + result = LoggingResult(events) + + suite = unittest.TestSuite() + + suite.run(result) + + self.assertEqual(events, []) + + # "Note that unlike TestCase.run(), TestSuite.run() requires the + # "result object to be passed in." + def test_run__requires_result(self): + suite = unittest.TestSuite() + + try: + suite.run() + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + def test_run(self): + events = [] + result = LoggingResult(events) + + class LoggingCase(unittest.TestCase): + def run(self, result): + events.append('run %s' % self._testMethodName) + + def test1(self): pass + def test2(self): pass + + tests = [LoggingCase('test1'), LoggingCase('test2')] + + unittest.TestSuite(tests).run(result) + + self.assertEqual(events, ['run test1', 'run test2']) + + # "Add a TestCase ... to the suite" + def test_addTest__TestCase(self): + class Foo(unittest.TestCase): + def test(self): pass + + test = Foo('test') + suite = unittest.TestSuite() + + suite.addTest(test) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [test]) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 1) + + # "Add a ... TestSuite to the suite" + def test_addTest__TestSuite(self): + class Foo(unittest.TestCase): + def test(self): pass + + suite_2 = unittest.TestSuite([Foo('test')]) + + suite = unittest.TestSuite() + suite.addTest(suite_2) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [suite_2]) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 1) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + def test_addTests(self): + class Foo(unittest.TestCase): + def test_1(self): pass + def test_2(self): pass + + test_1 = Foo('test_1') + test_2 = Foo('test_2') + inner_suite = unittest.TestSuite([test_2]) + + def gen(): + yield test_1 + yield test_2 + yield inner_suite + + suite_1 = unittest.TestSuite() + suite_1.addTests(gen()) + + self.assertEqual(list(suite_1), list(gen())) + + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + suite_2 = unittest.TestSuite() + for t in gen(): + suite_2.addTest(t) + + self.assertEqual(suite_1, suite_2) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # What happens if it doesn't get an iterable? + def test_addTest__noniterable(self): + suite = unittest.TestSuite() + + try: + suite.addTests(5) + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + def test_addTest__noncallable(self): + suite = unittest.TestSuite() + self.assertRaises(TypeError, suite.addTest, 5) + + def test_addTest__casesuiteclass(self): + suite = unittest.TestSuite() + self.assertRaises(TypeError, suite.addTest, Test_TestSuite) + self.assertRaises(TypeError, suite.addTest, unittest.TestSuite) + + def test_addTests__string(self): + suite = unittest.TestSuite() + self.assertRaises(TypeError, suite.addTests, "foo") + + def test_function_in_suite(self): + def f(_): + pass + suite = unittest.TestSuite() + suite.addTest(f) + + # when the bug is fixed this line will not crash + suite.run(unittest.TestResult()) + + def test_remove_test_at_index(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + suite = unittest.TestSuite() + + suite._tests = [1, 2, 3] + suite._removeTestAtIndex(1) + + self.assertEqual([1, None, 3], suite._tests) + + def test_remove_test_at_index_not_indexable(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + suite = unittest.TestSuite() + suite._tests = None + + # if _removeAtIndex raises for noniterables this next line will break + suite._removeTestAtIndex(2) + + def assert_garbage_collect_test_after_run(self, TestSuiteClass): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + class Foo(unittest.TestCase): + def test_nothing(self): + pass + + test = Foo('test_nothing') + wref = weakref.ref(test) + + suite = TestSuiteClass([wref()]) + suite.run(unittest.TestResult()) + + del test + + # for the benefit of non-reference counting implementations + gc.collect() + + self.assertEqual(suite._tests, [None]) + self.assertIsNone(wref()) + + def test_garbage_collect_test_after_run_BaseTestSuite(self): + self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite) + + def test_garbage_collect_test_after_run_TestSuite(self): + self.assert_garbage_collect_test_after_run(unittest.TestSuite) + + def test_basetestsuite(self): + class Test(unittest.TestCase): + wasSetUp = False + wasTornDown = False + @classmethod + def setUpClass(cls): + cls.wasSetUp = True + @classmethod + def tearDownClass(cls): + cls.wasTornDown = True + def testPass(self): + pass + def testFail(self): + fail + class Module(object): + wasSetUp = False + wasTornDown = False + @staticmethod + def setUpModule(): + Module.wasSetUp = True + @staticmethod + def tearDownModule(): + Module.wasTornDown = True + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + self.addCleanup(sys.modules.pop, 'Module') + + suite = unittest.BaseTestSuite() + suite.addTests([Test('testPass'), Test('testFail')]) + self.assertEqual(suite.countTestCases(), 2) + + result = unittest.TestResult() + suite.run(result) + self.assertFalse(Module.wasSetUp) + self.assertFalse(Module.wasTornDown) + self.assertFalse(Test.wasSetUp) + self.assertFalse(Test.wasTornDown) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 2) + self.assertEqual(suite.countTestCases(), 2) + + + def test_overriding_call(self): + class MySuite(unittest.TestSuite): + called = False + def __call__(self, *args, **kw): + self.called = True + unittest.TestSuite.__call__(self, *args, **kw) + + suite = MySuite() + result = unittest.TestResult() + wrapper = unittest.TestSuite() + wrapper.addTest(suite) + wrapper(result) + self.assertTrue(suite.called) + + # reusing results should be permitted even if abominable + self.assertFalse(result._testRunEntered) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py b/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py new file mode 100644 index 0000000000..87d7ae994d --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py @@ -0,0 +1,17 @@ +import os +import sys +import unittest + + +here = os.path.dirname(__file__) +loader = unittest.defaultTestLoader + +def load_tests(*args): + suite = unittest.TestSuite() + for fn in os.listdir(here): + if fn.startswith("test") and fn.endswith(".py"): + modname = "unittest.test.testmock." + fn[:-3] + __import__(modname) + module = sys.modules[modname] + suite.addTest(loader.loadTestsFromModule(module)) + return suite diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py b/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py new file mode 100644 index 0000000000..45c633a4ee --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py @@ -0,0 +1,18 @@ +import os +import unittest + + +def load_tests(loader, standard_tests, pattern): + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + pattern = pattern or "test*.py" + # We are inside unittest.test.testmock, so the top-level is three notches up + top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir))) + package_tests = loader.discover(start_dir=this_dir, pattern=pattern, + top_level_dir=top_level_dir) + standard_tests.addTests(package_tests) + return standard_tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/support.py b/Monika After Story/game/python-packages/unittest/test/testmock/support.py new file mode 100644 index 0000000000..49986d65dc --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/support.py @@ -0,0 +1,16 @@ +target = {'foo': 'FOO'} + + +def is_instance(obj, klass): + """Version of is_instance that doesn't access __class__""" + return issubclass(type(obj), klass) + + +class SomeClass(object): + class_attribute = None + + def wibble(self): pass + + +class X(object): + pass diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py b/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py new file mode 100644 index 0000000000..690ca4f55f --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py @@ -0,0 +1,1059 @@ +import asyncio +import gc +import inspect +import re +import unittest +from contextlib import contextmanager + +from asyncio import run, iscoroutinefunction +from unittest import IsolatedAsyncioTestCase +from unittest.mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, + create_autospec, sentinel, _CallList) + + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + + +class AsyncClass: + def __init__(self): pass + async def async_method(self): pass + def normal_method(self): pass + + @classmethod + async def async_class_method(cls): pass + + @staticmethod + async def async_static_method(): pass + + +class AwaitableClass: + def __await__(self): yield + +async def async_func(): pass + +async def async_func_args(a, b, *, c): pass + +def normal_func(): pass + +class NormalClass(object): + def a(self): pass + + +async_foo_name = f'{__name__}.AsyncClass' +normal_foo_name = f'{__name__}.NormalClass' + + +@contextmanager +def assertNeverAwaited(test): + with test.assertWarnsRegex(RuntimeWarning, "was never awaited$"): + yield + # In non-CPython implementations of Python, this is needed because timely + # deallocation is not guaranteed by the garbage collector. + gc.collect() + + +class AsyncPatchDecoratorTest(unittest.TestCase): + def test_is_coroutine_function_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertTrue(iscoroutinefunction(mock_method)) + test_async() + + def test_is_async_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + @patch(f'{async_foo_name}.async_method') + def test_no_parent_attribute(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + test_async() + test_no_parent_attribute() + + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_is_AsyncMock_patch_staticmethod(self): + @patch.object(AsyncClass, 'async_static_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_is_AsyncMock_patch_classmethod(self): + @patch.object(AsyncClass, 'async_class_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_patch(self): + @patch(f"{__name__}.async_func", return_value=1) + @patch(f"{__name__}.async_func_args", return_value=2) + async def test_async(func_args_mock, func_mock): + self.assertEqual(func_args_mock._mock_name, "async_func_args") + self.assertEqual(func_mock._mock_name, "async_func") + + self.assertIsInstance(async_func, AsyncMock) + self.assertIsInstance(async_func_args, AsyncMock) + + self.assertEqual(await async_func(), 1) + self.assertEqual(await async_func_args(1, 2, c=3), 2) + + run(test_async()) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + +class AsyncPatchCMTest(unittest.TestCase): + def test_is_async_function_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertTrue(iscoroutinefunction(mock_method)) + + test_async() + + def test_is_async_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + test_async() + + def test_is_AsyncMock_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_cm(self): + async def test_async(): + with patch(f"{__name__}.async_func", AsyncMock()): + self.assertIsInstance(async_func, AsyncMock) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + run(test_async()) + + +class AsyncMockTest(unittest.TestCase): + def test_iscoroutinefunction_default(self): + mock = AsyncMock() + self.assertTrue(iscoroutinefunction(mock)) + + def test_iscoroutinefunction_function(self): + async def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_isawaitable(self): + mock = AsyncMock() + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + self.assertIn('assert_awaited', dir(mock)) + + def test_iscoroutinefunction_normal_function(self): + def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_future_isfuture(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + fut = asyncio.Future() + loop.stop() + loop.close() + mock = AsyncMock(fut) + self.assertIsInstance(mock, asyncio.Future) + + +class AsyncAutospecTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch(async_foo_name, autospec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method.async_method, AsyncMock) + self.assertIsInstance(mock_method, MagicMock) + + @patch(async_foo_name, autospec=True) + def test_normal_method(mock_method): + self.assertIsInstance(mock_method.normal_method, MagicMock) + + test_async() + test_normal_method() + + def test_create_autospec_instance(self): + with self.assertRaises(RuntimeError): + create_autospec(async_func, instance=True) + + def test_create_autospec_awaitable_class(self): + awaitable_mock = create_autospec(spec=AwaitableClass()) + self.assertIsInstance(create_autospec(awaitable_mock), AsyncMock) + + def test_create_autospec(self): + spec = create_autospec(async_func_args) + awaitable = spec(1, 2, c=3) + async def main(): + await awaitable + + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + spec.assert_not_awaited() + + run(main()) + + self.assertTrue(iscoroutinefunction(spec)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertEqual(spec.await_count, 1) + self.assertEqual(spec.await_args, call(1, 2, c=3)) + self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) + spec.assert_awaited_once() + spec.assert_awaited_once_with(1, 2, c=3) + spec.assert_awaited_with(1, 2, c=3) + spec.assert_awaited() + + with self.assertRaises(AssertionError): + spec.assert_any_await(e=1) + + + def test_patch_with_autospec(self): + + async def test_async(): + with patch(f"{__name__}.async_func_args", autospec=True) as mock_method: + awaitable = mock_method(1, 2, c=3) + self.assertIsInstance(mock_method.mock, AsyncMock) + + self.assertTrue(iscoroutinefunction(mock_method)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.isawaitable(awaitable)) + + # Verify the default values during mock setup + self.assertEqual(mock_method.await_count, 0) + self.assertEqual(mock_method.await_args_list, []) + self.assertIsNone(mock_method.await_args) + mock_method.assert_not_awaited() + + await awaitable + + self.assertEqual(mock_method.await_count, 1) + self.assertEqual(mock_method.await_args, call(1, 2, c=3)) + self.assertEqual(mock_method.await_args_list, [call(1, 2, c=3)]) + mock_method.assert_awaited_once() + mock_method.assert_awaited_once_with(1, 2, c=3) + mock_method.assert_awaited_with(1, 2, c=3) + mock_method.assert_awaited() + + mock_method.reset_mock() + self.assertEqual(mock_method.await_count, 0) + self.assertIsNone(mock_method.await_args) + self.assertEqual(mock_method.await_args_list, []) + + run(test_async()) + + +class AsyncSpecTest(unittest.TestCase): + def test_spec_normal_methods_on_class(self): + def inner_test(mock_type): + mock = mock_type(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test method types with {mock_type}"): + inner_test(mock_type) + + def test_spec_normal_methods_on_class_with_mock(self): + mock = Mock(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, Mock) + + def test_spec_mock_type_kw(self): + def inner_test(mock_type): + async_mock = mock_type(spec=async_func) + self.assertIsInstance(async_mock, mock_type) + with assertNeverAwaited(self): + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(spec=normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec kwarg with {mock_type}"): + inner_test(mock_type) + + def test_spec_mock_type_positional(self): + def inner_test(mock_type): + async_mock = mock_type(async_func) + self.assertIsInstance(async_mock, mock_type) + with assertNeverAwaited(self): + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec positional with {mock_type}"): + inner_test(mock_type) + + def test_spec_as_normal_kw_AsyncMock(self): + mock = AsyncMock(spec=normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + def test_spec_as_normal_positional_AsyncMock(self): + mock = AsyncMock(normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + run(m) + + def test_spec_async_mock(self): + @patch.object(AsyncClass, 'async_method', spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_spec_parent_not_async_attribute_is(self): + @patch(async_foo_name, spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertIsInstance(mock_method.async_method, AsyncMock) + + test_async() + + def test_target_async_spec_not(self): + @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) + def test_async_attribute(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertFalse(inspect.iscoroutine(mock_method)) + self.assertFalse(inspect.isawaitable(mock_method)) + + test_async_attribute() + + def test_target_not_async_spec_is(self): + @patch.object(NormalClass, 'a', spec=async_func) + def test_attribute_not_async_spec_is(mock_async_func): + self.assertIsInstance(mock_async_func, AsyncMock) + test_attribute_not_async_spec_is() + + def test_spec_async_attributes(self): + @patch(normal_foo_name, spec=AsyncClass) + def test_async_attributes_coroutines(MockNormalClass): + self.assertIsInstance(MockNormalClass.async_method, AsyncMock) + self.assertIsInstance(MockNormalClass, MagicMock) + + test_async_attributes_coroutines() + + +class AsyncSpecSetTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method', spec_set=True) + def test_async(async_method): + self.assertIsInstance(async_method, AsyncMock) + test_async() + + def test_is_async_AsyncMock(self): + mock = AsyncMock(spec_set=AsyncClass.async_method) + self.assertTrue(iscoroutinefunction(mock)) + self.assertIsInstance(mock, AsyncMock) + + def test_is_child_AsyncMock(self): + mock = MagicMock(spec_set=AsyncClass) + self.assertTrue(iscoroutinefunction(mock.async_method)) + self.assertFalse(iscoroutinefunction(mock.normal_method)) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + self.assertIsInstance(mock, MagicMock) + + def test_magicmock_lambda_spec(self): + mock_obj = MagicMock() + mock_obj.mock_func = MagicMock(spec=lambda x: x) + + with patch.object(mock_obj, "mock_func") as cm: + self.assertIsInstance(cm, MagicMock) + + +class AsyncArguments(IsolatedAsyncioTestCase): + async def test_add_return_value(self): + async def addition(self, var): pass + + mock = AsyncMock(addition, return_value=10) + output = await mock(5) + + self.assertEqual(output, 10) + + async def test_add_side_effect_exception(self): + async def addition(var): pass + mock = AsyncMock(addition, side_effect=Exception('err')) + with self.assertRaises(Exception): + await mock(5) + + async def test_add_side_effect_coroutine(self): + async def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = await mock(5) + self.assertEqual(result, 6) + + async def test_add_side_effect_normal_function(self): + def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = await mock(5) + self.assertEqual(result, 6) + + async def test_add_side_effect_iterable(self): + vals = [1, 2, 3] + mock = AsyncMock(side_effect=vals) + for item in vals: + self.assertEqual(await mock(), item) + + with self.assertRaises(StopAsyncIteration) as e: + await mock() + + async def test_add_side_effect_exception_iterable(self): + class SampleException(Exception): + pass + + vals = [1, SampleException("foo")] + mock = AsyncMock(side_effect=vals) + self.assertEqual(await mock(), 1) + + with self.assertRaises(SampleException) as e: + await mock() + + async def test_return_value_AsyncMock(self): + value = AsyncMock(return_value=10) + mock = AsyncMock(return_value=value) + result = await mock() + self.assertIs(result, value) + + async def test_return_value_awaitable(self): + fut = asyncio.Future() + fut.set_result(None) + mock = AsyncMock(return_value=fut) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + async def test_side_effect_awaitable_values(self): + fut = asyncio.Future() + fut.set_result(None) + + mock = AsyncMock(side_effect=[fut]) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + with self.assertRaises(StopAsyncIteration): + await mock() + + async def test_side_effect_is_AsyncMock(self): + effect = AsyncMock(return_value=10) + mock = AsyncMock(side_effect=effect) + + result = await mock() + self.assertEqual(result, 10) + + async def test_wraps_coroutine(self): + value = asyncio.Future() + + ran = False + async def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) + + async def test_wraps_normal_function(self): + value = 1 + + ran = False + def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) + + async def test_await_args_list_order(self): + async_mock = AsyncMock() + mock2 = async_mock(2) + mock1 = async_mock(1) + await mock1 + await mock2 + async_mock.assert_has_awaits([call(1), call(2)]) + self.assertEqual(async_mock.await_args_list, [call(1), call(2)]) + self.assertEqual(async_mock.call_args_list, [call(2), call(1)]) + + +class AsyncMagicMethods(unittest.TestCase): + def test_async_magic_methods_return_async_mocks(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + self.assertIsInstance(m_mock.__anext__, AsyncMock) + # __aiter__ is actually a synchronous object + # so should return a MagicMock + self.assertIsInstance(m_mock.__aiter__, MagicMock) + + def test_sync_magic_methods_return_magic_mocks(self): + a_mock = AsyncMock() + self.assertIsInstance(a_mock.__enter__, MagicMock) + self.assertIsInstance(a_mock.__exit__, MagicMock) + self.assertIsInstance(a_mock.__next__, MagicMock) + self.assertIsInstance(a_mock.__len__, MagicMock) + + def test_magicmock_has_async_magic_methods(self): + m_mock = MagicMock() + self.assertTrue(hasattr(m_mock, "__aenter__")) + self.assertTrue(hasattr(m_mock, "__aexit__")) + self.assertTrue(hasattr(m_mock, "__anext__")) + + def test_asyncmock_has_sync_magic_methods(self): + a_mock = AsyncMock() + self.assertTrue(hasattr(a_mock, "__enter__")) + self.assertTrue(hasattr(a_mock, "__exit__")) + self.assertTrue(hasattr(a_mock, "__next__")) + self.assertTrue(hasattr(a_mock, "__len__")) + + def test_magic_methods_are_async_functions(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + # AsyncMocks are also coroutine functions + self.assertTrue(iscoroutinefunction(m_mock.__aenter__)) + self.assertTrue(iscoroutinefunction(m_mock.__aexit__)) + +class AsyncContextManagerTest(unittest.TestCase): + + class WithAsyncContextManager: + async def __aenter__(self, *args, **kwargs): pass + + async def __aexit__(self, *args, **kwargs): pass + + class WithSyncContextManager: + def __enter__(self, *args, **kwargs): pass + + def __exit__(self, *args, **kwargs): pass + + class ProductionCode: + # Example real-world(ish) code + def __init__(self): + self.session = None + + async def main(self): + async with self.session.post('https://python.org') as response: + val = await response.json() + return val + + def test_set_return_value_of_aenter(self): + def inner_test(mock_type): + pc = self.ProductionCode() + pc.session = MagicMock(name='sessionmock') + cm = mock_type(name='magic_cm') + response = AsyncMock(name='response') + response.json = AsyncMock(return_value={'json': 123}) + cm.__aenter__.return_value = response + pc.session.post.return_value = cm + result = run(pc.main()) + self.assertEqual(result, {'json': 123}) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test set return value of aenter with {mock_type}"): + inner_test(mock_type) + + def test_mock_supports_async_context_manager(self): + def inner_test(mock_type): + called = False + cm = self.WithAsyncContextManager() + cm_mock = mock_type(cm) + + async def use_context_manager(): + nonlocal called + async with cm_mock as result: + called = True + return result + + cm_result = run(use_context_manager()) + self.assertTrue(called) + self.assertTrue(cm_mock.__aenter__.called) + self.assertTrue(cm_mock.__aexit__.called) + cm_mock.__aenter__.assert_awaited() + cm_mock.__aexit__.assert_awaited() + # We mock __aenter__ so it does not return self + self.assertIsNot(cm_mock, cm_result) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test context manager magics with {mock_type}"): + inner_test(mock_type) + + + def test_mock_customize_async_context_manager(self): + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + expected_result = object() + mock_instance.__aenter__.return_value = expected_result + + async def use_context_manager(): + async with mock_instance as result: + return result + + self.assertIs(run(use_context_manager()), expected_result) + + def test_mock_customize_async_context_manager_with_coroutine(self): + enter_called = False + exit_called = False + + async def enter_coroutine(*args): + nonlocal enter_called + enter_called = True + + async def exit_coroutine(*args): + nonlocal exit_called + exit_called = True + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + mock_instance.__aenter__ = enter_coroutine + mock_instance.__aexit__ = exit_coroutine + + async def use_context_manager(): + async with mock_instance: + pass + + run(use_context_manager()) + self.assertTrue(enter_called) + self.assertTrue(exit_called) + + def test_context_manager_raise_exception_by_default(self): + async def raise_in(context_manager): + async with context_manager: + raise TypeError() + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + with self.assertRaises(TypeError): + run(raise_in(mock_instance)) + + +class AsyncIteratorTest(unittest.TestCase): + class WithAsyncIterator(object): + def __init__(self): + self.items = ["foo", "NormalFoo", "baz"] + + def __aiter__(self): pass + + async def __anext__(self): pass + + def test_aiter_set_return_value(self): + mock_iter = AsyncMock(name="tester") + mock_iter.__aiter__.return_value = [1, 2, 3] + async def main(): + return [i async for i in mock_iter] + result = run(main()) + self.assertEqual(result, [1, 2, 3]) + + def test_mock_aiter_and_anext_asyncmock(self): + def inner_test(mock_type): + instance = self.WithAsyncIterator() + mock_instance = mock_type(instance) + # Check that the mock and the real thing bahave the same + # __aiter__ is not actually async, so not a coroutinefunction + self.assertFalse(iscoroutinefunction(instance.__aiter__)) + self.assertFalse(iscoroutinefunction(mock_instance.__aiter__)) + # __anext__ is async + self.assertTrue(iscoroutinefunction(instance.__anext__)) + self.assertTrue(iscoroutinefunction(mock_instance.__anext__)) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test aiter and anext corourtine with {mock_type}"): + inner_test(mock_type) + + + def test_mock_async_for(self): + async def iterate(iterator): + accumulator = [] + async for item in iterator: + accumulator.append(item) + + return accumulator + + expected = ["FOO", "BAR", "BAZ"] + def test_default(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + self.assertEqual(run(iterate(mock_instance)), []) + + + def test_set_return_value(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = expected[:] + self.assertEqual(run(iterate(mock_instance)), expected) + + def test_set_return_value_iter(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = iter(expected[:]) + self.assertEqual(run(iterate(mock_instance)), expected) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"default value with {mock_type}"): + test_default(mock_type) + + with self.subTest(f"set return_value with {mock_type}"): + test_set_return_value(mock_type) + + with self.subTest(f"set return_value iterator with {mock_type}"): + test_set_return_value_iter(mock_type) + + +class AsyncMockAssert(unittest.TestCase): + def setUp(self): + self.mock = AsyncMock() + + async def _runnable_test(self, *args, **kwargs): + await self.mock(*args, **kwargs) + + async def _await_coroutine(self, coroutine): + return await coroutine + + def test_assert_called_but_not_awaited(self): + mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + mock.async_method() + self.assertTrue(iscoroutinefunction(mock.async_method)) + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.assert_awaited() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + def test_assert_called_then_awaited(self): + mock = AsyncMock(AsyncClass) + mock_coroutine = mock.async_method() + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + run(self._await_coroutine(mock_coroutine)) + # Assert we haven't re-called the function + mock.async_method.assert_called_once() + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + mock.async_method.assert_awaited_once_with() + + def test_assert_called_and_awaited_at_same_time(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + with self.assertRaises(AssertionError): + self.mock.assert_called() + + run(self._runnable_test()) + self.mock.assert_called_once() + self.mock.assert_awaited_once() + + def test_assert_called_twice_and_awaited_once(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + # The first call will be awaited so no warning there + # But this call will never get awaited, so it will warn here + with assertNeverAwaited(self): + mock.async_method() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + mock.async_method.assert_called() + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + + def test_assert_called_once_and_awaited_twice(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + mock.async_method.assert_called_once() + run(self._await_coroutine(coroutine)) + with self.assertRaises(RuntimeError): + # Cannot reuse already awaited coroutine + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + + def test_assert_awaited_but_not_called(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + with self.assertRaises(TypeError): + # You cannot await an AsyncMock, it must be a coroutine + run(self._await_coroutine(self.mock)) + + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + + def test_assert_has_calls_not_awaits(self): + kalls = [call('foo')] + with assertNeverAwaited(self): + self.mock('foo') + self.mock.assert_has_calls(kalls) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(kalls) + + def test_assert_has_mock_calls_on_async_mock_no_spec(self): + with assertNeverAwaited(self): + self.mock() + kalls_empty = [('', (), {})] + self.assertEqual(self.mock.mock_calls, kalls_empty) + + with assertNeverAwaited(self): + self.mock('foo') + with assertNeverAwaited(self): + self.mock('baz') + mock_kalls = ([call(), call('foo'), call('baz')]) + self.assertEqual(self.mock.mock_calls, mock_kalls) + + def test_assert_has_mock_calls_on_async_mock_with_spec(self): + a_class_mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + a_class_mock.async_method() + kalls_empty = [('', (), {})] + self.assertEqual(a_class_mock.async_method.mock_calls, kalls_empty) + self.assertEqual(a_class_mock.mock_calls, [call.async_method()]) + + with assertNeverAwaited(self): + a_class_mock.async_method(1, 2, 3, a=4, b=5) + method_kalls = [call(), call(1, 2, 3, a=4, b=5)] + mock_kalls = [call.async_method(), call.async_method(1, 2, 3, a=4, b=5)] + self.assertEqual(a_class_mock.async_method.mock_calls, method_kalls) + self.assertEqual(a_class_mock.mock_calls, mock_kalls) + + def test_async_method_calls_recorded(self): + with assertNeverAwaited(self): + self.mock.something(3, fish=None) + with assertNeverAwaited(self): + self.mock.something_else.something(6, cake=sentinel.Cake) + + self.assertEqual(self.mock.method_calls, [ + ("something", (3,), {'fish': None}), + ("something_else.something", (6,), {'cake': sentinel.Cake}) + ], + "method calls not recorded correctly") + self.assertEqual(self.mock.something_else.method_calls, + [("something", (6,), {'cake': sentinel.Cake})], + "method calls not recorded correctly") + + def test_async_arg_lists(self): + def assert_attrs(mock): + names = ('call_args_list', 'method_calls', 'mock_calls') + for name in names: + attr = getattr(mock, name) + self.assertIsInstance(attr, _CallList) + self.assertIsInstance(attr, list) + self.assertEqual(attr, []) + + assert_attrs(self.mock) + with assertNeverAwaited(self): + self.mock() + with assertNeverAwaited(self): + self.mock(1, 2) + with assertNeverAwaited(self): + self.mock(a=3) + + self.mock.reset_mock() + assert_attrs(self.mock) + + a_mock = AsyncMock(AsyncClass) + with assertNeverAwaited(self): + a_mock.async_method() + with assertNeverAwaited(self): + a_mock.async_method(1, a=3) + + a_mock.reset_mock() + assert_attrs(a_mock) + + def test_assert_awaited(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + run(self._runnable_test()) + self.mock.assert_awaited() + + def test_assert_awaited_once(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + run(self._runnable_test()) + self.mock.assert_awaited_once() + + run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + def test_assert_awaited_with(self): + msg = 'Not awaited' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + + run(self._runnable_test()) + msg = 'expected await not found' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + + run(self._runnable_test('foo')) + self.mock.assert_awaited_with('foo') + + run(self._runnable_test('SomethingElse')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_with('foo') + + def test_assert_awaited_once_with(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + run(self._runnable_test('foo')) + self.mock.assert_awaited_once_with('foo') + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + def test_assert_any_wait(self): + with self.assertRaises(AssertionError): + self.mock.assert_any_await('foo') + + run(self._runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_any_await('foo') + + run(self._runnable_test('foo')) + self.mock.assert_any_await('foo') + + run(self._runnable_test('SomethingElse')) + self.mock.assert_any_await('foo') + + def test_assert_has_awaits_no_order(self): + calls = [call('foo'), call('baz')] + + with self.assertRaises(AssertionError) as cm: + self.mock.assert_has_awaits(calls) + self.assertEqual(len(cm.exception.args), 1) + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('baz')) + self.mock.assert_has_awaits(calls) + + run(self._runnable_test('SomethingElse')) + self.mock.assert_has_awaits(calls) + + def test_awaits_asserts_with_any(self): + class Foo: + def __eq__(self, other): pass + + run(self._runnable_test(Foo(), 1)) + + self.mock.assert_has_awaits([call(ANY, 1)]) + self.mock.assert_awaited_with(ANY, 1) + self.mock.assert_any_await(ANY, 1) + + def test_awaits_asserts_with_spec_and_any(self): + class Foo: + def __eq__(self, other): pass + + mock_with_spec = AsyncMock(spec=Foo) + + async def _custom_mock_runnable_test(*args): + await mock_with_spec(*args) + + run(_custom_mock_runnable_test(Foo(), 1)) + mock_with_spec.assert_has_awaits([call(ANY, 1)]) + mock_with_spec.assert_awaited_with(ANY, 1) + mock_with_spec.assert_any_await(ANY, 1) + + def test_assert_has_awaits_ordered(self): + calls = [call('foo'), call('baz')] + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('bamf')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('foo')) + self.mock.assert_has_awaits(calls, any_order=True) + + run(self._runnable_test('qux')) + self.mock.assert_has_awaits(calls, any_order=True) + + def test_assert_not_awaited(self): + self.mock.assert_not_awaited() + + run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_not_awaited() + + def test_assert_has_awaits_not_matching_spec_error(self): + async def f(x=None): pass + + self.mock = AsyncMock(spec=f) + run(self._runnable_test(1)) + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape('Awaits not found.\n' + 'Expected: [call()]\n' + 'Actual: [call(1)]'))) as cm: + self.mock.assert_has_awaits([call()]) + self.assertIsNone(cm.exception.__cause__) + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape( + 'Error processing expected awaits.\n' + "Errors: [None, TypeError('too many positional " + "arguments')]\n" + 'Expected: [call(), call(1, 2)]\n' + 'Actual: [call(1)]'))) as cm: + self.mock.assert_has_awaits([call(), call(1, 2)]) + self.assertIsInstance(cm.exception.__cause__, TypeError) diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py b/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py new file mode 100644 index 0000000000..5eadc00704 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py @@ -0,0 +1,150 @@ +# Copyright (C) 2007-2012 Michael Foord & the mock team +# E-mail: fuzzyman AT voidspace DOT org DOT uk +# http://www.voidspace.org.uk/python/mock/ + +import unittest +from unittest.test.testmock.support import is_instance, X, SomeClass + +from unittest.mock import ( + Mock, MagicMock, NonCallableMagicMock, + NonCallableMock, patch, create_autospec, + CallableMixin +) + + + +class TestCallable(unittest.TestCase): + + def assertNotCallable(self, mock): + self.assertTrue(is_instance(mock, NonCallableMagicMock)) + self.assertFalse(is_instance(mock, CallableMixin)) + + + def test_non_callable(self): + for mock in NonCallableMagicMock(), NonCallableMock(): + self.assertRaises(TypeError, mock) + self.assertFalse(hasattr(mock, '__call__')) + self.assertIn(mock.__class__.__name__, repr(mock)) + + + def test_hierarchy(self): + self.assertTrue(issubclass(MagicMock, Mock)) + self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) + + + def test_attributes(self): + one = NonCallableMock() + self.assertTrue(issubclass(type(one.one), Mock)) + + two = NonCallableMagicMock() + self.assertTrue(issubclass(type(two.two), MagicMock)) + + + def test_subclasses(self): + class MockSub(Mock): + pass + + one = MockSub() + self.assertTrue(issubclass(type(one.one), MockSub)) + + class MagicSub(MagicMock): + pass + + two = MagicSub() + self.assertTrue(issubclass(type(two.two), MagicSub)) + + + def test_patch_spec(self): + patcher = patch('%s.X' % __name__, spec=True) + mock = patcher.start() + self.addCleanup(patcher.stop) + + instance = mock() + mock.assert_called_once_with() + + self.assertNotCallable(instance) + self.assertRaises(TypeError, instance) + + + def test_patch_spec_set(self): + patcher = patch('%s.X' % __name__, spec_set=True) + mock = patcher.start() + self.addCleanup(patcher.stop) + + instance = mock() + mock.assert_called_once_with() + + self.assertNotCallable(instance) + self.assertRaises(TypeError, instance) + + + def test_patch_spec_instance(self): + patcher = patch('%s.X' % __name__, spec=X()) + mock = patcher.start() + self.addCleanup(patcher.stop) + + self.assertNotCallable(mock) + self.assertRaises(TypeError, mock) + + + def test_patch_spec_set_instance(self): + patcher = patch('%s.X' % __name__, spec_set=X()) + mock = patcher.start() + self.addCleanup(patcher.stop) + + self.assertNotCallable(mock) + self.assertRaises(TypeError, mock) + + + def test_patch_spec_callable_class(self): + class CallableX(X): + def __call__(self): pass + + class Sub(CallableX): + pass + + class Multi(SomeClass, Sub): + pass + + for arg in 'spec', 'spec_set': + for Klass in CallableX, Sub, Multi: + with patch('%s.X' % __name__, **{arg: Klass}) as mock: + instance = mock() + mock.assert_called_once_with() + + self.assertTrue(is_instance(instance, MagicMock)) + # inherited spec + self.assertRaises(AttributeError, getattr, instance, + 'foobarbaz') + + result = instance() + # instance is callable, result has no spec + instance.assert_called_once_with() + + result(3, 2, 1) + result.assert_called_once_with(3, 2, 1) + result.foo(3, 2, 1) + result.foo.assert_called_once_with(3, 2, 1) + + + def test_create_autospec(self): + mock = create_autospec(X) + instance = mock() + self.assertRaises(TypeError, instance) + + mock = create_autospec(X()) + self.assertRaises(TypeError, mock) + + + def test_create_autospec_instance(self): + mock = create_autospec(SomeClass, instance=True) + + self.assertRaises(TypeError, mock) + mock.wibble() + mock.wibble.assert_called_once_with() + + self.assertRaises(TypeError, mock.wibble, 'some', 'args') + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py b/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py new file mode 100644 index 0000000000..9e7ec5d62d --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py @@ -0,0 +1,1127 @@ +import inspect +import time +import types +import unittest + +from unittest.mock import ( + call, _Call, create_autospec, MagicMock, + Mock, ANY, _CallList, patch, PropertyMock, _callable +) + +from datetime import datetime +from functools import partial + +class SomeClass(object): + def one(self, a, b): pass + def two(self): pass + def three(self, a=None): pass + + + +class AnyTest(unittest.TestCase): + + def test_any(self): + self.assertEqual(ANY, object()) + + mock = Mock() + mock(ANY) + mock.assert_called_with(ANY) + + mock = Mock() + mock(foo=ANY) + mock.assert_called_with(foo=ANY) + + def test_repr(self): + self.assertEqual(repr(ANY), '') + self.assertEqual(str(ANY), '') + + + def test_any_and_datetime(self): + mock = Mock() + mock(datetime.now(), foo=datetime.now()) + + mock.assert_called_with(ANY, foo=ANY) + + + def test_any_mock_calls_comparison_order(self): + mock = Mock() + class Foo(object): + def __eq__(self, other): pass + def __ne__(self, other): pass + + for d in datetime.now(), Foo(): + mock.reset_mock() + + mock(d, foo=d, bar=d) + mock.method(d, zinga=d, alpha=d) + mock().method(a1=d, z99=d) + + expected = [ + call(ANY, foo=ANY, bar=ANY), + call.method(ANY, zinga=ANY, alpha=ANY), + call(), call().method(a1=ANY, z99=ANY) + ] + self.assertEqual(expected, mock.mock_calls) + self.assertEqual(mock.mock_calls, expected) + + def test_any_no_spec(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock() + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) + + def test_any_and_spec_set(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock(spec=Foo) + + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) + +class CallTest(unittest.TestCase): + + def test_call_with_call(self): + kall = _Call() + self.assertEqual(kall, _Call()) + self.assertEqual(kall, _Call(('',))) + self.assertEqual(kall, _Call(((),))) + self.assertEqual(kall, _Call(({},))) + self.assertEqual(kall, _Call(('', ()))) + self.assertEqual(kall, _Call(('', {}))) + self.assertEqual(kall, _Call(('', (), {}))) + self.assertEqual(kall, _Call(('foo',))) + self.assertEqual(kall, _Call(('bar', ()))) + self.assertEqual(kall, _Call(('baz', {}))) + self.assertEqual(kall, _Call(('spam', (), {}))) + + kall = _Call(((1, 2, 3),)) + self.assertEqual(kall, _Call(((1, 2, 3),))) + self.assertEqual(kall, _Call(('', (1, 2, 3)))) + self.assertEqual(kall, _Call(((1, 2, 3), {}))) + self.assertEqual(kall, _Call(('', (1, 2, 3), {}))) + + kall = _Call(((1, 2, 4),)) + self.assertNotEqual(kall, _Call(('', (1, 2, 3)))) + self.assertNotEqual(kall, _Call(('', (1, 2, 3), {}))) + + kall = _Call(('foo', (1, 2, 4),)) + self.assertNotEqual(kall, _Call(('', (1, 2, 4)))) + self.assertNotEqual(kall, _Call(('', (1, 2, 4), {}))) + self.assertNotEqual(kall, _Call(('bar', (1, 2, 4)))) + self.assertNotEqual(kall, _Call(('bar', (1, 2, 4), {}))) + + kall = _Call(({'a': 3},)) + self.assertEqual(kall, _Call(('', (), {'a': 3}))) + self.assertEqual(kall, _Call(('', {'a': 3}))) + self.assertEqual(kall, _Call(((), {'a': 3}))) + self.assertEqual(kall, _Call(({'a': 3},))) + + + def test_empty__Call(self): + args = _Call() + + self.assertEqual(args, ()) + self.assertEqual(args, ('foo',)) + self.assertEqual(args, ((),)) + self.assertEqual(args, ('foo', ())) + self.assertEqual(args, ('foo',(), {})) + self.assertEqual(args, ('foo', {})) + self.assertEqual(args, ({},)) + + + def test_named_empty_call(self): + args = _Call(('foo', (), {})) + + self.assertEqual(args, ('foo',)) + self.assertEqual(args, ('foo', ())) + self.assertEqual(args, ('foo',(), {})) + self.assertEqual(args, ('foo', {})) + + self.assertNotEqual(args, ((),)) + self.assertNotEqual(args, ()) + self.assertNotEqual(args, ({},)) + self.assertNotEqual(args, ('bar',)) + self.assertNotEqual(args, ('bar', ())) + self.assertNotEqual(args, ('bar', {})) + + + def test_call_with_args(self): + args = _Call(((1, 2, 3), {})) + + self.assertEqual(args, ((1, 2, 3),)) + self.assertEqual(args, ('foo', (1, 2, 3))) + self.assertEqual(args, ('foo', (1, 2, 3), {})) + self.assertEqual(args, ((1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) + + + def test_named_call_with_args(self): + args = _Call(('foo', (1, 2, 3), {})) + + self.assertEqual(args, ('foo', (1, 2, 3))) + self.assertEqual(args, ('foo', (1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) + + self.assertNotEqual(args, ((1, 2, 3),)) + self.assertNotEqual(args, ((1, 2, 3), {})) + + + def test_call_with_kwargs(self): + args = _Call(((), dict(a=3, b=4))) + + self.assertEqual(args, (dict(a=3, b=4),)) + self.assertEqual(args, ('foo', dict(a=3, b=4))) + self.assertEqual(args, ('foo', (), dict(a=3, b=4))) + self.assertEqual(args, ((), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) + + + def test_named_call_with_kwargs(self): + args = _Call(('foo', (), dict(a=3, b=4))) + + self.assertEqual(args, ('foo', dict(a=3, b=4))) + self.assertEqual(args, ('foo', (), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) + + self.assertNotEqual(args, (dict(a=3, b=4),)) + self.assertNotEqual(args, ((), dict(a=3, b=4))) + + + def test_call_with_args_call_empty_name(self): + args = _Call(((1, 2, 3), {})) + + self.assertEqual(args, call(1, 2, 3)) + self.assertEqual(call(1, 2, 3), args) + self.assertIn(call(1, 2, 3), [args]) + + + def test_call_ne(self): + self.assertNotEqual(_Call(((1, 2, 3),)), call(1, 2)) + self.assertFalse(_Call(((1, 2, 3),)) != call(1, 2, 3)) + self.assertTrue(_Call(((1, 2), {})) != call(1, 2, 3)) + + + def test_call_non_tuples(self): + kall = _Call(((1, 2, 3),)) + for value in 1, None, self, int: + self.assertNotEqual(kall, value) + self.assertFalse(kall == value) + + + def test_repr(self): + self.assertEqual(repr(_Call()), 'call()') + self.assertEqual(repr(_Call(('foo',))), 'call.foo()') + + self.assertEqual(repr(_Call(((1, 2, 3), {'a': 'b'}))), + "call(1, 2, 3, a='b')") + self.assertEqual(repr(_Call(('bar', (1, 2, 3), {'a': 'b'}))), + "call.bar(1, 2, 3, a='b')") + + self.assertEqual(repr(call), 'call') + self.assertEqual(str(call), 'call') + + self.assertEqual(repr(call()), 'call()') + self.assertEqual(repr(call(1)), 'call(1)') + self.assertEqual(repr(call(zz='thing')), "call(zz='thing')") + + self.assertEqual(repr(call().foo), 'call().foo') + self.assertEqual(repr(call(1).foo.bar(a=3).bing), + 'call().foo.bar().bing') + self.assertEqual( + repr(call().foo(1, 2, a=3)), + "call().foo(1, 2, a=3)" + ) + self.assertEqual(repr(call()()), "call()()") + self.assertEqual(repr(call(1)(2)), "call()(2)") + self.assertEqual( + repr(call()().bar().baz.beep(1)), + "call()().bar().baz.beep(1)" + ) + + + def test_call(self): + self.assertEqual(call(), ('', (), {})) + self.assertEqual(call('foo', 'bar', one=3, two=4), + ('', ('foo', 'bar'), {'one': 3, 'two': 4})) + + mock = Mock() + mock(1, 2, 3) + mock(a=3, b=6) + self.assertEqual(mock.call_args_list, + [call(1, 2, 3), call(a=3, b=6)]) + + def test_attribute_call(self): + self.assertEqual(call.foo(1), ('foo', (1,), {})) + self.assertEqual(call.bar.baz(fish='eggs'), + ('bar.baz', (), {'fish': 'eggs'})) + + mock = Mock() + mock.foo(1, 2 ,3) + mock.bar.baz(a=3, b=6) + self.assertEqual(mock.method_calls, + [call.foo(1, 2, 3), call.bar.baz(a=3, b=6)]) + + + def test_extended_call(self): + result = call(1).foo(2).bar(3, a=4) + self.assertEqual(result, ('().foo().bar', (3,), dict(a=4))) + + mock = MagicMock() + mock(1, 2, a=3, b=4) + self.assertEqual(mock.call_args, call(1, 2, a=3, b=4)) + self.assertNotEqual(mock.call_args, call(1, 2, 3)) + + self.assertEqual(mock.call_args_list, [call(1, 2, a=3, b=4)]) + self.assertEqual(mock.mock_calls, [call(1, 2, a=3, b=4)]) + + mock = MagicMock() + mock.foo(1).bar()().baz.beep(a=6) + + last_call = call.foo(1).bar()().baz.beep(a=6) + self.assertEqual(mock.mock_calls[-1], last_call) + self.assertEqual(mock.mock_calls, last_call.call_list()) + + + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_call_list(self): + mock = MagicMock() + mock(1) + self.assertEqual(call(1).call_list(), mock.mock_calls) + + mock = MagicMock() + mock(1).method(2) + self.assertEqual(call(1).method(2).call_list(), + mock.mock_calls) + + mock = MagicMock() + mock(1).method(2)(3) + self.assertEqual(call(1).method(2)(3).call_list(), + mock.mock_calls) + + mock = MagicMock() + int(mock(1).method(2)(3).foo.bar.baz(4)(5)) + kall = call(1).method(2)(3).foo.bar.baz(4)(5).__int__() + self.assertEqual(kall.call_list(), mock.mock_calls) + + + def test_call_any(self): + self.assertEqual(call, ANY) + + m = MagicMock() + int(m) + self.assertEqual(m.mock_calls, [ANY]) + self.assertEqual([ANY], m.mock_calls) + + + def test_two_args_call(self): + args = _Call(((1, 2), {'a': 3}), two=True) + self.assertEqual(len(args), 2) + self.assertEqual(args[0], (1, 2)) + self.assertEqual(args[1], {'a': 3}) + + other_args = _Call(((1, 2), {'a': 3})) + self.assertEqual(args, other_args) + + def test_call_with_name(self): + self.assertEqual(_Call((), 'foo')[0], 'foo') + self.assertEqual(_Call((('bar', 'barz'),),)[0], '') + self.assertEqual(_Call((('bar', 'barz'), {'hello': 'world'}),)[0], '') + + def test_dunder_call(self): + m = MagicMock() + m().foo()['bar']() + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__getitem__('bar'), call().foo().__getitem__()()] + ) + m = MagicMock() + m().foo()['bar'] = 1 + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__setitem__('bar', 1)] + ) + m = MagicMock() + iter(m().foo()) + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__iter__()] + ) + + +class SpecSignatureTest(unittest.TestCase): + + def _check_someclass_mock(self, mock): + self.assertRaises(AttributeError, getattr, mock, 'foo') + mock.one(1, 2) + mock.one.assert_called_with(1, 2) + self.assertRaises(AssertionError, + mock.one.assert_called_with, 3, 4) + self.assertRaises(TypeError, mock.one, 1) + + mock.two() + mock.two.assert_called_with() + self.assertRaises(AssertionError, + mock.two.assert_called_with, 3) + self.assertRaises(TypeError, mock.two, 1) + + mock.three() + mock.three.assert_called_with() + self.assertRaises(AssertionError, + mock.three.assert_called_with, 3) + self.assertRaises(TypeError, mock.three, 3, 2) + + mock.three(1) + mock.three.assert_called_with(1) + + mock.three(a=1) + mock.three.assert_called_with(a=1) + + + def test_basic(self): + mock = create_autospec(SomeClass) + self._check_someclass_mock(mock) + mock = create_autospec(SomeClass()) + self._check_someclass_mock(mock) + + + def test_create_autospec_return_value(self): + def f(): pass + mock = create_autospec(f, return_value='foo') + self.assertEqual(mock(), 'foo') + + class Foo(object): + pass + + mock = create_autospec(Foo, return_value='foo') + self.assertEqual(mock(), 'foo') + + + def test_autospec_reset_mock(self): + m = create_autospec(int) + int(m) + m.reset_mock() + self.assertEqual(m.__int__.call_count, 0) + + + def test_mocking_unbound_methods(self): + class Foo(object): + def foo(self, foo): pass + p = patch.object(Foo, 'foo') + mock_foo = p.start() + Foo().foo(1) + + mock_foo.assert_called_with(1) + + + def test_create_autospec_keyword_arguments(self): + class Foo(object): + a = 3 + m = create_autospec(Foo, a='3') + self.assertEqual(m.a, '3') + + + def test_create_autospec_keyword_only_arguments(self): + def foo(a, *, b=None): pass + + m = create_autospec(foo) + m(1) + m.assert_called_with(1) + self.assertRaises(TypeError, m, 1, 2) + + m(2, b=3) + m.assert_called_with(2, b=3) + + + def test_function_as_instance_attribute(self): + obj = SomeClass() + def f(a): pass + obj.f = f + + mock = create_autospec(obj) + mock.f('bing') + mock.f.assert_called_with('bing') + + + def test_spec_as_list(self): + # because spec as a list of strings in the mock constructor means + # something very different we treat a list instance as the type. + mock = create_autospec([]) + mock.append('foo') + mock.append.assert_called_with('foo') + + self.assertRaises(AttributeError, getattr, mock, 'foo') + + class Foo(object): + foo = [] + + mock = create_autospec(Foo) + mock.foo.append(3) + mock.foo.append.assert_called_with(3) + self.assertRaises(AttributeError, getattr, mock.foo, 'foo') + + + def test_attributes(self): + class Sub(SomeClass): + attr = SomeClass() + + sub_mock = create_autospec(Sub) + + for mock in (sub_mock, sub_mock.attr): + self._check_someclass_mock(mock) + + + def test_spec_has_descriptor_returning_function(self): + + class CrazyDescriptor(object): + + def __get__(self, obj, type_): + if obj is None: + return lambda x: None + + class MyClass(object): + + some_attr = CrazyDescriptor() + + mock = create_autospec(MyClass) + mock.some_attr(1) + with self.assertRaises(TypeError): + mock.some_attr() + with self.assertRaises(TypeError): + mock.some_attr(1, 2) + + + def test_spec_has_function_not_in_bases(self): + + class CrazyClass(object): + + def __dir__(self): + return super(CrazyClass, self).__dir__()+['crazy'] + + def __getattr__(self, item): + if item == 'crazy': + return lambda x: x + raise AttributeError(item) + + inst = CrazyClass() + with self.assertRaises(AttributeError): + inst.other + self.assertEqual(inst.crazy(42), 42) + + mock = create_autospec(inst) + mock.crazy(42) + with self.assertRaises(TypeError): + mock.crazy() + with self.assertRaises(TypeError): + mock.crazy(1, 2) + + + def test_builtin_functions_types(self): + # we could replace builtin functions / methods with a function + # with *args / **kwargs signature. Using the builtin method type + # as a spec seems to work fairly well though. + class BuiltinSubclass(list): + def bar(self, arg): pass + sorted = sorted + attr = {} + + mock = create_autospec(BuiltinSubclass) + mock.append(3) + mock.append.assert_called_with(3) + self.assertRaises(AttributeError, getattr, mock.append, 'foo') + + mock.bar('foo') + mock.bar.assert_called_with('foo') + self.assertRaises(TypeError, mock.bar, 'foo', 'bar') + self.assertRaises(AttributeError, getattr, mock.bar, 'foo') + + mock.sorted([1, 2]) + mock.sorted.assert_called_with([1, 2]) + self.assertRaises(AttributeError, getattr, mock.sorted, 'foo') + + mock.attr.pop(3) + mock.attr.pop.assert_called_with(3) + self.assertRaises(AttributeError, getattr, mock.attr, 'foo') + + + def test_method_calls(self): + class Sub(SomeClass): + attr = SomeClass() + + mock = create_autospec(Sub) + mock.one(1, 2) + mock.two() + mock.three(3) + + expected = [call.one(1, 2), call.two(), call.three(3)] + self.assertEqual(mock.method_calls, expected) + + mock.attr.one(1, 2) + mock.attr.two() + mock.attr.three(3) + + expected.extend( + [call.attr.one(1, 2), call.attr.two(), call.attr.three(3)] + ) + self.assertEqual(mock.method_calls, expected) + + + def test_magic_methods(self): + class BuiltinSubclass(list): + attr = {} + + mock = create_autospec(BuiltinSubclass) + self.assertEqual(list(mock), []) + self.assertRaises(TypeError, int, mock) + self.assertRaises(TypeError, int, mock.attr) + self.assertEqual(list(mock), []) + + self.assertIsInstance(mock['foo'], MagicMock) + self.assertIsInstance(mock.attr['foo'], MagicMock) + + + def test_spec_set(self): + class Sub(SomeClass): + attr = SomeClass() + + for spec in (Sub, Sub()): + mock = create_autospec(spec, spec_set=True) + self._check_someclass_mock(mock) + + self.assertRaises(AttributeError, setattr, mock, 'foo', 'bar') + self.assertRaises(AttributeError, setattr, mock.attr, 'foo', 'bar') + + + def test_descriptors(self): + class Foo(object): + @classmethod + def f(cls, a, b): pass + @staticmethod + def g(a, b): pass + + class Bar(Foo): pass + + class Baz(SomeClass, Bar): pass + + for spec in (Foo, Foo(), Bar, Bar(), Baz, Baz()): + mock = create_autospec(spec) + mock.f(1, 2) + mock.f.assert_called_once_with(1, 2) + + mock.g(3, 4) + mock.g.assert_called_once_with(3, 4) + + + def test_recursive(self): + class A(object): + def a(self): pass + foo = 'foo bar baz' + bar = foo + + A.B = A + mock = create_autospec(A) + + mock() + self.assertFalse(mock.B.called) + + mock.a() + mock.B.a() + self.assertEqual(mock.method_calls, [call.a(), call.B.a()]) + + self.assertIs(A.foo, A.bar) + self.assertIsNot(mock.foo, mock.bar) + mock.foo.lower() + self.assertRaises(AssertionError, mock.bar.lower.assert_called_with) + + + def test_spec_inheritance_for_classes(self): + class Foo(object): + def a(self, x): pass + class Bar(object): + def f(self, y): pass + + class_mock = create_autospec(Foo) + + self.assertIsNot(class_mock, class_mock()) + + for this_mock in class_mock, class_mock(): + this_mock.a(x=5) + this_mock.a.assert_called_with(x=5) + this_mock.a.assert_called_with(5) + self.assertRaises(TypeError, this_mock.a, 'foo', 'bar') + self.assertRaises(AttributeError, getattr, this_mock, 'b') + + instance_mock = create_autospec(Foo()) + instance_mock.a(5) + instance_mock.a.assert_called_with(5) + instance_mock.a.assert_called_with(x=5) + self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar') + self.assertRaises(AttributeError, getattr, instance_mock, 'b') + + # The return value isn't isn't callable + self.assertRaises(TypeError, instance_mock) + + instance_mock.Bar.f(6) + instance_mock.Bar.f.assert_called_with(6) + instance_mock.Bar.f.assert_called_with(y=6) + self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g') + + instance_mock.Bar().f(6) + instance_mock.Bar().f.assert_called_with(6) + instance_mock.Bar().f.assert_called_with(y=6) + self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g') + + + def test_inherit(self): + class Foo(object): + a = 3 + + Foo.Foo = Foo + + # class + mock = create_autospec(Foo) + instance = mock() + self.assertRaises(AttributeError, getattr, instance, 'b') + + attr_instance = mock.Foo() + self.assertRaises(AttributeError, getattr, attr_instance, 'b') + + # instance + mock = create_autospec(Foo()) + self.assertRaises(AttributeError, getattr, mock, 'b') + self.assertRaises(TypeError, mock) + + # attribute instance + call_result = mock.Foo() + self.assertRaises(AttributeError, getattr, call_result, 'b') + + + def test_builtins(self): + # used to fail with infinite recursion + create_autospec(1) + + create_autospec(int) + create_autospec('foo') + create_autospec(str) + create_autospec({}) + create_autospec(dict) + create_autospec([]) + create_autospec(list) + create_autospec(set()) + create_autospec(set) + create_autospec(1.0) + create_autospec(float) + create_autospec(1j) + create_autospec(complex) + create_autospec(False) + create_autospec(True) + + + def test_function(self): + def f(a, b): pass + + mock = create_autospec(f) + self.assertRaises(TypeError, mock) + mock(1, 2) + mock.assert_called_with(1, 2) + mock.assert_called_with(1, b=2) + mock.assert_called_with(a=1, b=2) + + f.f = f + mock = create_autospec(f) + self.assertRaises(TypeError, mock.f) + mock.f(3, 4) + mock.f.assert_called_with(3, 4) + mock.f.assert_called_with(a=3, b=4) + + + def test_skip_attributeerrors(self): + class Raiser(object): + def __get__(self, obj, type=None): + if obj is None: + raise AttributeError('Can only be accessed via an instance') + + class RaiserClass(object): + raiser = Raiser() + + @staticmethod + def existing(a, b): + return a + b + + self.assertEqual(RaiserClass.existing(1, 2), 3) + s = create_autospec(RaiserClass) + self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) + self.assertEqual(s.existing(1, 2), s.existing.return_value) + self.assertRaises(AttributeError, lambda: s.nonexisting) + + # check we can fetch the raiser attribute and it has no spec + obj = s.raiser + obj.foo, obj.bar + + + def test_signature_class(self): + class Foo(object): + def __init__(self, a, b=3): pass + + mock = create_autospec(Foo) + + self.assertRaises(TypeError, mock) + mock(1) + mock.assert_called_once_with(1) + mock.assert_called_once_with(a=1) + self.assertRaises(AssertionError, mock.assert_called_once_with, 2) + + mock(4, 5) + mock.assert_called_with(4, 5) + mock.assert_called_with(a=4, b=5) + self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4) + + + def test_class_with_no_init(self): + # this used to raise an exception + # due to trying to get a signature from object.__init__ + class Foo(object): + pass + create_autospec(Foo) + + + def test_signature_callable(self): + class Callable(object): + def __init__(self, x, y): pass + def __call__(self, a): pass + + mock = create_autospec(Callable) + mock(1, 2) + mock.assert_called_once_with(1, 2) + mock.assert_called_once_with(x=1, y=2) + self.assertRaises(TypeError, mock, 'a') + + instance = mock(1, 2) + self.assertRaises(TypeError, instance) + instance(a='a') + instance.assert_called_once_with('a') + instance.assert_called_once_with(a='a') + instance('a') + instance.assert_called_with('a') + instance.assert_called_with(a='a') + + mock = create_autospec(Callable(1, 2)) + mock(a='a') + mock.assert_called_once_with(a='a') + self.assertRaises(TypeError, mock) + mock('a') + mock.assert_called_with('a') + + + def test_signature_noncallable(self): + class NonCallable(object): + def __init__(self): + pass + + mock = create_autospec(NonCallable) + instance = mock() + mock.assert_called_once_with() + self.assertRaises(TypeError, mock, 'a') + self.assertRaises(TypeError, instance) + self.assertRaises(TypeError, instance, 'a') + + mock = create_autospec(NonCallable()) + self.assertRaises(TypeError, mock) + self.assertRaises(TypeError, mock, 'a') + + + def test_create_autospec_none(self): + class Foo(object): + bar = None + + mock = create_autospec(Foo) + none = mock.bar + self.assertNotIsInstance(none, type(None)) + + none.foo() + none.foo.assert_called_once_with() + + + def test_autospec_functions_with_self_in_odd_place(self): + class Foo(object): + def f(a, self): pass + + a = create_autospec(Foo) + a.f(10) + a.f.assert_called_with(10) + a.f.assert_called_with(self=10) + a.f(self=10) + a.f.assert_called_with(10) + a.f.assert_called_with(self=10) + + + def test_autospec_data_descriptor(self): + class Descriptor(object): + def __init__(self, value): + self.value = value + + def __get__(self, obj, cls=None): + return self + + def __set__(self, obj, value): pass + + class MyProperty(property): + pass + + class Foo(object): + __slots__ = ['slot'] + + @property + def prop(self): pass + + @MyProperty + def subprop(self): pass + + desc = Descriptor(42) + + foo = create_autospec(Foo) + + def check_data_descriptor(mock_attr): + # Data descriptors don't have a spec. + self.assertIsInstance(mock_attr, MagicMock) + mock_attr(1, 2, 3) + mock_attr.abc(4, 5, 6) + mock_attr.assert_called_once_with(1, 2, 3) + mock_attr.abc.assert_called_once_with(4, 5, 6) + + # property + check_data_descriptor(foo.prop) + # property subclass + check_data_descriptor(foo.subprop) + # class __slot__ + check_data_descriptor(foo.slot) + # plain data descriptor + check_data_descriptor(foo.desc) + + + def test_autospec_on_bound_builtin_function(self): + meth = types.MethodType(time.ctime, time.time()) + self.assertIsInstance(meth(), str) + mocked = create_autospec(meth) + + # no signature, so no spec to check against + mocked() + mocked.assert_called_once_with() + mocked.reset_mock() + mocked(4, 5, 6) + mocked.assert_called_once_with(4, 5, 6) + + + def test_autospec_getattr_partial_function(self): + # bpo-32153 : getattr returning partial functions without + # __name__ should not create AttributeError in create_autospec + class Foo: + + def __getattr__(self, attribute): + return partial(lambda name: name, attribute) + + proxy = Foo() + autospec = create_autospec(proxy) + self.assertFalse(hasattr(autospec, '__name__')) + + + def test_spec_inspect_signature(self): + + def myfunc(x, y): pass + + mock = create_autospec(myfunc) + mock(1, 2) + mock(x=1, y=2) + + self.assertEqual(inspect.signature(mock), inspect.signature(myfunc)) + self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) + self.assertRaises(TypeError, mock, 1) + + + def test_spec_inspect_signature_annotations(self): + + def foo(a: int, b: int=10, *, c:int) -> int: + return a + b + c + + self.assertEqual(foo(1, 2 , c=3), 6) + mock = create_autospec(foo) + mock(1, 2, c=3) + mock(1, c=3) + + self.assertEqual(inspect.signature(mock), inspect.signature(foo)) + self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) + self.assertRaises(TypeError, mock, 1) + self.assertRaises(TypeError, mock, 1, 2, 3, c=4) + + + def test_spec_function_no_name(self): + func = lambda: 'nope' + mock = create_autospec(func) + self.assertEqual(mock.__name__, 'funcopy') + + + def test_spec_function_assert_has_calls(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_has_calls([call(1)]) + with self.assertRaises(AssertionError): + mock.assert_has_calls([call(2)]) + + + def test_spec_function_assert_any_call(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_any_call(1) + with self.assertRaises(AssertionError): + mock.assert_any_call(2) + + + def test_spec_function_reset_mock(self): + def f(a): pass + rv = Mock() + mock = create_autospec(f, return_value=rv) + mock(1)(2) + self.assertEqual(mock.mock_calls, [call(1)]) + self.assertEqual(rv.mock_calls, [call(2)]) + mock.reset_mock() + self.assertEqual(mock.mock_calls, []) + self.assertEqual(rv.mock_calls, []) + + +class TestCallList(unittest.TestCase): + + def test_args_list_contains_call_list(self): + mock = Mock() + self.assertIsInstance(mock.call_args_list, _CallList) + + mock(1, 2) + mock(a=3) + mock(3, 4) + mock(b=6) + + for kall in call(1, 2), call(a=3), call(3, 4), call(b=6): + self.assertIn(kall, mock.call_args_list) + + calls = [call(a=3), call(3, 4)] + self.assertIn(calls, mock.call_args_list) + calls = [call(1, 2), call(a=3)] + self.assertIn(calls, mock.call_args_list) + calls = [call(3, 4), call(b=6)] + self.assertIn(calls, mock.call_args_list) + calls = [call(3, 4)] + self.assertIn(calls, mock.call_args_list) + + self.assertNotIn(call('fish'), mock.call_args_list) + self.assertNotIn([call('fish')], mock.call_args_list) + + + def test_call_list_str(self): + mock = Mock() + mock(1, 2) + mock.foo(a=3) + mock.foo.bar().baz('fish', cat='dog') + + expected = ( + "[call(1, 2),\n" + " call.foo(a=3),\n" + " call.foo.bar(),\n" + " call.foo.bar().baz('fish', cat='dog')]" + ) + self.assertEqual(str(mock.mock_calls), expected) + + + def test_propertymock(self): + p = patch('%s.SomeClass.one' % __name__, new_callable=PropertyMock) + mock = p.start() + try: + SomeClass.one + mock.assert_called_once_with() + + s = SomeClass() + s.one + mock.assert_called_with() + self.assertEqual(mock.mock_calls, [call(), call()]) + + s.one = 3 + self.assertEqual(mock.mock_calls, [call(), call(), call(3)]) + finally: + p.stop() + + + def test_propertymock_returnvalue(self): + m = MagicMock() + p = PropertyMock() + type(m).foo = p + + returned = m.foo + p.assert_called_once_with() + self.assertIsInstance(returned, MagicMock) + self.assertNotIsInstance(returned, PropertyMock) + + +class TestCallablePredicate(unittest.TestCase): + + def test_type(self): + for obj in [str, bytes, int, list, tuple, SomeClass]: + self.assertTrue(_callable(obj)) + + def test_call_magic_method(self): + class Callable: + def __call__(self): pass + instance = Callable() + self.assertTrue(_callable(instance)) + + def test_staticmethod(self): + class WithStaticMethod: + @staticmethod + def staticfunc(): pass + self.assertTrue(_callable(WithStaticMethod.staticfunc)) + + def test_non_callable_staticmethod(self): + class BadStaticMethod: + not_callable = staticmethod(None) + self.assertFalse(_callable(BadStaticMethod.not_callable)) + + def test_classmethod(self): + class WithClassMethod: + @classmethod + def classfunc(cls): pass + self.assertTrue(_callable(WithClassMethod.classfunc)) + + def test_non_callable_classmethod(self): + class BadClassMethod: + not_callable = classmethod(None) + self.assertFalse(_callable(BadClassMethod.not_callable)) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py b/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py new file mode 100644 index 0000000000..a4feae7e9d --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py @@ -0,0 +1,509 @@ +import math +import unittest +import os +from asyncio import iscoroutinefunction +from unittest.mock import AsyncMock, Mock, MagicMock, _magics + + + +class TestMockingMagicMethods(unittest.TestCase): + + def test_deleting_magic_methods(self): + mock = Mock() + self.assertFalse(hasattr(mock, '__getitem__')) + + mock.__getitem__ = Mock() + self.assertTrue(hasattr(mock, '__getitem__')) + + del mock.__getitem__ + self.assertFalse(hasattr(mock, '__getitem__')) + + + def test_magicmock_del(self): + mock = MagicMock() + # before using getitem + del mock.__getitem__ + self.assertRaises(TypeError, lambda: mock['foo']) + + mock = MagicMock() + # this time use it first + mock['foo'] + del mock.__getitem__ + self.assertRaises(TypeError, lambda: mock['foo']) + + + def test_magic_method_wrapping(self): + mock = Mock() + def f(self, name): + return self, 'fish' + + mock.__getitem__ = f + self.assertIsNot(mock.__getitem__, f) + self.assertEqual(mock['foo'], (mock, 'fish')) + self.assertEqual(mock.__getitem__('foo'), (mock, 'fish')) + + mock.__getitem__ = mock + self.assertIs(mock.__getitem__, mock) + + + def test_magic_methods_isolated_between_mocks(self): + mock1 = Mock() + mock2 = Mock() + + mock1.__iter__ = Mock(return_value=iter([])) + self.assertEqual(list(mock1), []) + self.assertRaises(TypeError, lambda: list(mock2)) + + + def test_repr(self): + mock = Mock() + self.assertEqual(repr(mock), "" % id(mock)) + mock.__repr__ = lambda s: 'foo' + self.assertEqual(repr(mock), 'foo') + + + def test_str(self): + mock = Mock() + self.assertEqual(str(mock), object.__str__(mock)) + mock.__str__ = lambda s: 'foo' + self.assertEqual(str(mock), 'foo') + + + def test_dict_methods(self): + mock = Mock() + + self.assertRaises(TypeError, lambda: mock['foo']) + def _del(): + del mock['foo'] + def _set(): + mock['foo'] = 3 + self.assertRaises(TypeError, _del) + self.assertRaises(TypeError, _set) + + _dict = {} + def getitem(s, name): + return _dict[name] + def setitem(s, name, value): + _dict[name] = value + def delitem(s, name): + del _dict[name] + + mock.__setitem__ = setitem + mock.__getitem__ = getitem + mock.__delitem__ = delitem + + self.assertRaises(KeyError, lambda: mock['foo']) + mock['foo'] = 'bar' + self.assertEqual(_dict, {'foo': 'bar'}) + self.assertEqual(mock['foo'], 'bar') + del mock['foo'] + self.assertEqual(_dict, {}) + + + def test_numeric(self): + original = mock = Mock() + mock.value = 0 + + self.assertRaises(TypeError, lambda: mock + 3) + + def add(self, other): + mock.value += other + return self + mock.__add__ = add + self.assertEqual(mock + 3, mock) + self.assertEqual(mock.value, 3) + + del mock.__add__ + def iadd(mock): + mock += 3 + self.assertRaises(TypeError, iadd, mock) + mock.__iadd__ = add + mock += 6 + self.assertEqual(mock, original) + self.assertEqual(mock.value, 9) + + self.assertRaises(TypeError, lambda: 3 + mock) + mock.__radd__ = add + self.assertEqual(7 + mock, mock) + self.assertEqual(mock.value, 16) + + def test_division(self): + original = mock = Mock() + mock.value = 32 + self.assertRaises(TypeError, lambda: mock / 2) + + def truediv(self, other): + mock.value /= other + return self + mock.__truediv__ = truediv + self.assertEqual(mock / 2, mock) + self.assertEqual(mock.value, 16) + + del mock.__truediv__ + def itruediv(mock): + mock /= 4 + self.assertRaises(TypeError, itruediv, mock) + mock.__itruediv__ = truediv + mock /= 8 + self.assertEqual(mock, original) + self.assertEqual(mock.value, 2) + + self.assertRaises(TypeError, lambda: 8 / mock) + mock.__rtruediv__ = truediv + self.assertEqual(0.5 / mock, mock) + self.assertEqual(mock.value, 4) + + def test_hash(self): + mock = Mock() + # test delegation + self.assertEqual(hash(mock), Mock.__hash__(mock)) + + def _hash(s): + return 3 + mock.__hash__ = _hash + self.assertEqual(hash(mock), 3) + + + def test_nonzero(self): + m = Mock() + self.assertTrue(bool(m)) + + m.__bool__ = lambda s: False + self.assertFalse(bool(m)) + + + def test_comparison(self): + mock = Mock() + def comp(s, o): + return True + mock.__lt__ = mock.__gt__ = mock.__le__ = mock.__ge__ = comp + self. assertTrue(mock < 3) + self. assertTrue(mock > 3) + self. assertTrue(mock <= 3) + self. assertTrue(mock >= 3) + + self.assertRaises(TypeError, lambda: MagicMock() < object()) + self.assertRaises(TypeError, lambda: object() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > object()) + self.assertRaises(TypeError, lambda: object() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= object()) + self.assertRaises(TypeError, lambda: object() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= object()) + self.assertRaises(TypeError, lambda: object() >= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) + + + def test_equality(self): + for mock in Mock(), MagicMock(): + self.assertEqual(mock == mock, True) + self.assertIsInstance(mock == mock, bool) + self.assertEqual(mock != mock, False) + self.assertIsInstance(mock != mock, bool) + self.assertEqual(mock == object(), False) + self.assertEqual(mock != object(), True) + + def eq(self, other): + return other == 3 + mock.__eq__ = eq + self.assertTrue(mock == 3) + self.assertFalse(mock == 4) + + def ne(self, other): + return other == 3 + mock.__ne__ = ne + self.assertTrue(mock != 3) + self.assertFalse(mock != 4) + + mock = MagicMock() + mock.__eq__.return_value = True + self.assertIsInstance(mock == 3, bool) + self.assertEqual(mock == 3, True) + + mock.__ne__.return_value = False + self.assertIsInstance(mock != 3, bool) + self.assertEqual(mock != 3, False) + + + def test_len_contains_iter(self): + mock = Mock() + + self.assertRaises(TypeError, len, mock) + self.assertRaises(TypeError, iter, mock) + self.assertRaises(TypeError, lambda: 'foo' in mock) + + mock.__len__ = lambda s: 6 + self.assertEqual(len(mock), 6) + + mock.__contains__ = lambda s, o: o == 3 + self.assertIn(3, mock) + self.assertNotIn(6, mock) + + mock.__iter__ = lambda s: iter('foobarbaz') + self.assertEqual(list(mock), list('foobarbaz')) + + + def test_magicmock(self): + mock = MagicMock() + + mock.__iter__.return_value = iter([1, 2, 3]) + self.assertEqual(list(mock), [1, 2, 3]) + + getattr(mock, '__bool__').return_value = False + self.assertFalse(hasattr(mock, '__nonzero__')) + self.assertFalse(bool(mock)) + + for entry in _magics: + self.assertTrue(hasattr(mock, entry)) + self.assertFalse(hasattr(mock, '__imaginary__')) + + + def test_magic_mock_equality(self): + mock = MagicMock() + self.assertIsInstance(mock == object(), bool) + self.assertIsInstance(mock != object(), bool) + + self.assertEqual(mock == object(), False) + self.assertEqual(mock != object(), True) + self.assertEqual(mock == mock, True) + self.assertEqual(mock != mock, False) + + def test_asyncmock_defaults(self): + mock = AsyncMock() + self.assertEqual(int(mock), 1) + self.assertEqual(complex(mock), 1j) + self.assertEqual(float(mock), 1.0) + self.assertNotIn(object(), mock) + self.assertEqual(len(mock), 0) + self.assertEqual(list(mock), []) + self.assertEqual(hash(mock), object.__hash__(mock)) + self.assertEqual(str(mock), object.__str__(mock)) + self.assertTrue(bool(mock)) + self.assertEqual(round(mock), mock.__round__()) + self.assertEqual(math.trunc(mock), mock.__trunc__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') + self.assertEqual(hex(mock), '0x1') + # how to test __sizeof__ ? + + def test_magicmock_defaults(self): + mock = MagicMock() + self.assertEqual(int(mock), 1) + self.assertEqual(complex(mock), 1j) + self.assertEqual(float(mock), 1.0) + self.assertNotIn(object(), mock) + self.assertEqual(len(mock), 0) + self.assertEqual(list(mock), []) + self.assertEqual(hash(mock), object.__hash__(mock)) + self.assertEqual(str(mock), object.__str__(mock)) + self.assertTrue(bool(mock)) + self.assertEqual(round(mock), mock.__round__()) + self.assertEqual(math.trunc(mock), mock.__trunc__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') + self.assertEqual(hex(mock), '0x1') + # how to test __sizeof__ ? + + + def test_magic_methods_fspath(self): + mock = MagicMock() + expected_path = mock.__fspath__() + mock.reset_mock() + + self.assertEqual(os.fspath(mock), expected_path) + mock.__fspath__.assert_called_once() + + + def test_magic_methods_and_spec(self): + class Iterable(object): + def __iter__(self): pass + + mock = Mock(spec=Iterable) + self.assertRaises(AttributeError, lambda: mock.__iter__) + + mock.__iter__ = Mock(return_value=iter([])) + self.assertEqual(list(mock), []) + + class NonIterable(object): + pass + mock = Mock(spec=NonIterable) + self.assertRaises(AttributeError, lambda: mock.__iter__) + + def set_int(): + mock.__int__ = Mock(return_value=iter([])) + self.assertRaises(AttributeError, set_int) + + mock = MagicMock(spec=Iterable) + self.assertEqual(list(mock), []) + self.assertRaises(AttributeError, set_int) + + + def test_magic_methods_and_spec_set(self): + class Iterable(object): + def __iter__(self): pass + + mock = Mock(spec_set=Iterable) + self.assertRaises(AttributeError, lambda: mock.__iter__) + + mock.__iter__ = Mock(return_value=iter([])) + self.assertEqual(list(mock), []) + + class NonIterable(object): + pass + mock = Mock(spec_set=NonIterable) + self.assertRaises(AttributeError, lambda: mock.__iter__) + + def set_int(): + mock.__int__ = Mock(return_value=iter([])) + self.assertRaises(AttributeError, set_int) + + mock = MagicMock(spec_set=Iterable) + self.assertEqual(list(mock), []) + self.assertRaises(AttributeError, set_int) + + + def test_setting_unsupported_magic_method(self): + mock = MagicMock() + def set_setattr(): + mock.__setattr__ = lambda self, name: None + self.assertRaisesRegex(AttributeError, + "Attempting to set unsupported magic method '__setattr__'.", + set_setattr + ) + + + def test_attributes_and_return_value(self): + mock = MagicMock() + attr = mock.foo + def _get_type(obj): + # the type of every mock (or magicmock) is a custom subclass + # so the real type is the second in the mro + return type(obj).__mro__[1] + self.assertEqual(_get_type(attr), MagicMock) + + returned = mock() + self.assertEqual(_get_type(returned), MagicMock) + + + def test_magic_methods_are_magic_mocks(self): + mock = MagicMock() + self.assertIsInstance(mock.__getitem__, MagicMock) + + mock[1][2].__getitem__.return_value = 3 + self.assertEqual(mock[1][2][3], 3) + + + def test_magic_method_reset_mock(self): + mock = MagicMock() + str(mock) + self.assertTrue(mock.__str__.called) + mock.reset_mock() + self.assertFalse(mock.__str__.called) + + + def test_dir(self): + # overriding the default implementation + for mock in Mock(), MagicMock(): + def _dir(self): + return ['foo'] + mock.__dir__ = _dir + self.assertEqual(dir(mock), ['foo']) + + + def test_bound_methods(self): + m = Mock() + + # XXXX should this be an expected failure instead? + + # this seems like it should work, but is hard to do without introducing + # other api inconsistencies. Failure message could be better though. + m.__iter__ = [3].__iter__ + self.assertRaises(TypeError, iter, m) + + + def test_magic_method_type(self): + class Foo(MagicMock): + pass + + foo = Foo() + self.assertIsInstance(foo.__int__, Foo) + + + def test_descriptor_from_class(self): + m = MagicMock() + type(m).__str__.return_value = 'foo' + self.assertEqual(str(m), 'foo') + + + def test_iterable_as_iter_return_value(self): + m = MagicMock() + m.__iter__.return_value = [1, 2, 3] + self.assertEqual(list(m), [1, 2, 3]) + self.assertEqual(list(m), [1, 2, 3]) + + m.__iter__.return_value = iter([4, 5, 6]) + self.assertEqual(list(m), [4, 5, 6]) + self.assertEqual(list(m), []) + + + def test_matmul(self): + m = MagicMock() + self.assertIsInstance(m @ 1, MagicMock) + m.__matmul__.return_value = 42 + m.__rmatmul__.return_value = 666 + m.__imatmul__.return_value = 24 + self.assertEqual(m @ 1, 42) + self.assertEqual(1 @ m, 666) + m @= 24 + self.assertEqual(m, 24) + + def test_divmod_and_rdivmod(self): + m = MagicMock() + self.assertIsInstance(divmod(5, m), MagicMock) + m.__divmod__.return_value = (2, 1) + self.assertEqual(divmod(m, 2), (2, 1)) + m = MagicMock() + foo = divmod(2, m) + self.assertIsInstance(foo, MagicMock) + foo_direct = m.__divmod__(2) + self.assertIsInstance(foo_direct, MagicMock) + bar = divmod(m, 2) + self.assertIsInstance(bar, MagicMock) + bar_direct = m.__rdivmod__(2) + self.assertIsInstance(bar_direct, MagicMock) + + # http://bugs.python.org/issue23310 + # Check if you can change behaviour of magic methods in MagicMock init + def test_magic_in_initialization(self): + m = MagicMock(**{'__str__.return_value': "12"}) + self.assertEqual(str(m), "12") + + def test_changing_magic_set_in_initialization(self): + m = MagicMock(**{'__str__.return_value': "12"}) + m.__str__.return_value = "13" + self.assertEqual(str(m), "13") + m = MagicMock(**{'__str__.return_value': "12"}) + m.configure_mock(**{'__str__.return_value': "14"}) + self.assertEqual(str(m), "14") + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py b/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py new file mode 100644 index 0000000000..f930724530 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py @@ -0,0 +1,2171 @@ +import copy +import re +import sys +import tempfile + +from test.support import ALWAYS_EQ +import unittest +from unittest.test.testmock.support import is_instance +from unittest import mock +from unittest.mock import ( + call, DEFAULT, patch, sentinel, + MagicMock, Mock, NonCallableMock, + NonCallableMagicMock, AsyncMock, _Call, _CallList, + create_autospec +) + + +class Iter(object): + def __init__(self): + self.thing = iter(['this', 'is', 'an', 'iter']) + + def __iter__(self): + return self + + def next(self): + return next(self.thing) + + __next__ = next + + +class Something(object): + def meth(self, a, b, c, d=None): pass + + @classmethod + def cmeth(cls, a, b, c, d=None): pass + + @staticmethod + def smeth(a, b, c, d=None): pass + + +def something(a): pass + + +class MockTest(unittest.TestCase): + + def test_all(self): + # if __all__ is badly defined then import * will raise an error + # We have to exec it because you can't import * inside a method + # in Python 3 + exec("from unittest.mock import *") + + + def test_constructor(self): + mock = Mock() + + self.assertFalse(mock.called, "called not initialised correctly") + self.assertEqual(mock.call_count, 0, + "call_count not initialised correctly") + self.assertTrue(is_instance(mock.return_value, Mock), + "return_value not initialised correctly") + + self.assertEqual(mock.call_args, None, + "call_args not initialised correctly") + self.assertEqual(mock.call_args_list, [], + "call_args_list not initialised correctly") + self.assertEqual(mock.method_calls, [], + "method_calls not initialised correctly") + + # Can't use hasattr for this test as it always returns True on a mock + self.assertNotIn('_items', mock.__dict__, + "default mock should not have '_items' attribute") + + self.assertIsNone(mock._mock_parent, + "parent not initialised correctly") + self.assertIsNone(mock._mock_methods, + "methods not initialised correctly") + self.assertEqual(mock._mock_children, {}, + "children not initialised incorrectly") + + + def test_return_value_in_constructor(self): + mock = Mock(return_value=None) + self.assertIsNone(mock.return_value, + "return value in constructor not honoured") + + + def test_change_return_value_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.return_value = 1 + self.assertEqual(mock(), 1) + + + def test_change_side_effect_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.side_effect = TypeError() + with self.assertRaises(TypeError): + mock() + + + def test_repr(self): + mock = Mock(name='foo') + self.assertIn('foo', repr(mock)) + self.assertIn("'%s'" % id(mock), repr(mock)) + + mocks = [(Mock(), 'mock'), (Mock(name='bar'), 'bar')] + for mock, name in mocks: + self.assertIn('%s.bar' % name, repr(mock.bar)) + self.assertIn('%s.foo()' % name, repr(mock.foo())) + self.assertIn('%s.foo().bing' % name, repr(mock.foo().bing)) + self.assertIn('%s()' % name, repr(mock())) + self.assertIn('%s()()' % name, repr(mock()())) + self.assertIn('%s()().foo.bar.baz().bing' % name, + repr(mock()().foo.bar.baz().bing)) + + + def test_repr_with_spec(self): + class X(object): + pass + + mock = Mock(spec=X) + self.assertIn(" spec='X' ", repr(mock)) + + mock = Mock(spec=X()) + self.assertIn(" spec='X' ", repr(mock)) + + mock = Mock(spec_set=X) + self.assertIn(" spec_set='X' ", repr(mock)) + + mock = Mock(spec_set=X()) + self.assertIn(" spec_set='X' ", repr(mock)) + + mock = Mock(spec=X, name='foo') + self.assertIn(" spec='X' ", repr(mock)) + self.assertIn(" name='foo' ", repr(mock)) + + mock = Mock(name='foo') + self.assertNotIn("spec", repr(mock)) + + mock = Mock() + self.assertNotIn("spec", repr(mock)) + + mock = Mock(spec=['foo']) + self.assertNotIn("spec", repr(mock)) + + + def test_side_effect(self): + mock = Mock() + + def effect(*args, **kwargs): + raise SystemError('kablooie') + + mock.side_effect = effect + self.assertRaises(SystemError, mock, 1, 2, fish=3) + mock.assert_called_with(1, 2, fish=3) + + results = [1, 2, 3] + def effect(): + return results.pop() + mock.side_effect = effect + + self.assertEqual([mock(), mock(), mock()], [3, 2, 1], + "side effect not used correctly") + + mock = Mock(side_effect=sentinel.SideEffect) + self.assertEqual(mock.side_effect, sentinel.SideEffect, + "side effect in constructor not used") + + def side_effect(): + return DEFAULT + mock = Mock(side_effect=side_effect, return_value=sentinel.RETURN) + self.assertEqual(mock(), sentinel.RETURN) + + def test_autospec_side_effect(self): + # Test for issue17826 + results = [1, 2, 3] + def effect(): + return results.pop() + def f(): pass + + mock = create_autospec(f) + mock.side_effect = [1, 2, 3] + self.assertEqual([mock(), mock(), mock()], [1, 2, 3], + "side effect not used correctly in create_autospec") + # Test where side effect is a callable + results = [1, 2, 3] + mock = create_autospec(f) + mock.side_effect = effect + self.assertEqual([mock(), mock(), mock()], [3, 2, 1], + "callable side effect not used correctly") + + def test_autospec_side_effect_exception(self): + # Test for issue 23661 + def f(): pass + + mock = create_autospec(f) + mock.side_effect = ValueError('Bazinga!') + self.assertRaisesRegex(ValueError, 'Bazinga!', mock) + + + def test_reset_mock(self): + parent = Mock() + spec = ["something"] + mock = Mock(name="child", parent=parent, spec=spec) + mock(sentinel.Something, something=sentinel.SomethingElse) + something = mock.something + mock.something() + mock.side_effect = sentinel.SideEffect + return_value = mock.return_value + return_value() + + mock.reset_mock() + + self.assertEqual(mock._mock_name, "child", + "name incorrectly reset") + self.assertEqual(mock._mock_parent, parent, + "parent incorrectly reset") + self.assertEqual(mock._mock_methods, spec, + "methods incorrectly reset") + + self.assertFalse(mock.called, "called not reset") + self.assertEqual(mock.call_count, 0, "call_count not reset") + self.assertEqual(mock.call_args, None, "call_args not reset") + self.assertEqual(mock.call_args_list, [], "call_args_list not reset") + self.assertEqual(mock.method_calls, [], + "method_calls not initialised correctly: %r != %r" % + (mock.method_calls, [])) + self.assertEqual(mock.mock_calls, []) + + self.assertEqual(mock.side_effect, sentinel.SideEffect, + "side_effect incorrectly reset") + self.assertEqual(mock.return_value, return_value, + "return_value incorrectly reset") + self.assertFalse(return_value.called, "return value mock not reset") + self.assertEqual(mock._mock_children, {'something': something}, + "children reset incorrectly") + self.assertEqual(mock.something, something, + "children incorrectly cleared") + self.assertFalse(mock.something.called, "child not reset") + + + def test_reset_mock_recursion(self): + mock = Mock() + mock.return_value = mock + + # used to cause recursion + mock.reset_mock() + + def test_reset_mock_on_mock_open_issue_18622(self): + a = mock.mock_open() + a.reset_mock() + + def test_call(self): + mock = Mock() + self.assertTrue(is_instance(mock.return_value, Mock), + "Default return_value should be a Mock") + + result = mock() + self.assertEqual(mock(), result, + "different result from consecutive calls") + mock.reset_mock() + + ret_val = mock(sentinel.Arg) + self.assertTrue(mock.called, "called not set") + self.assertEqual(mock.call_count, 1, "call_count incorrect") + self.assertEqual(mock.call_args, ((sentinel.Arg,), {}), + "call_args not set") + self.assertEqual(mock.call_args.args, (sentinel.Arg,), + "call_args not set") + self.assertEqual(mock.call_args.kwargs, {}, + "call_args not set") + self.assertEqual(mock.call_args_list, [((sentinel.Arg,), {})], + "call_args_list not initialised correctly") + + mock.return_value = sentinel.ReturnValue + ret_val = mock(sentinel.Arg, key=sentinel.KeyArg) + self.assertEqual(ret_val, sentinel.ReturnValue, + "incorrect return value") + + self.assertEqual(mock.call_count, 2, "call_count incorrect") + self.assertEqual(mock.call_args, + ((sentinel.Arg,), {'key': sentinel.KeyArg}), + "call_args not set") + self.assertEqual(mock.call_args_list, [ + ((sentinel.Arg,), {}), + ((sentinel.Arg,), {'key': sentinel.KeyArg}) + ], + "call_args_list not set") + + + def test_call_args_comparison(self): + mock = Mock() + mock() + mock(sentinel.Arg) + mock(kw=sentinel.Kwarg) + mock(sentinel.Arg, kw=sentinel.Kwarg) + self.assertEqual(mock.call_args_list, [ + (), + ((sentinel.Arg,),), + ({"kw": sentinel.Kwarg},), + ((sentinel.Arg,), {"kw": sentinel.Kwarg}) + ]) + self.assertEqual(mock.call_args, + ((sentinel.Arg,), {"kw": sentinel.Kwarg})) + self.assertEqual(mock.call_args.args, (sentinel.Arg,)) + self.assertEqual(mock.call_args.kwargs, {"kw": sentinel.Kwarg}) + + # Comparing call_args to a long sequence should not raise + # an exception. See issue 24857. + self.assertFalse(mock.call_args == "a long sequence") + + + def test_calls_equal_with_any(self): + # Check that equality and non-equality is consistent even when + # comparing with mock.ANY + mm = mock.MagicMock() + self.assertTrue(mm == mm) + self.assertFalse(mm != mm) + self.assertFalse(mm == mock.MagicMock()) + self.assertTrue(mm != mock.MagicMock()) + self.assertTrue(mm == mock.ANY) + self.assertFalse(mm != mock.ANY) + self.assertTrue(mock.ANY == mm) + self.assertFalse(mock.ANY != mm) + self.assertTrue(mm == ALWAYS_EQ) + self.assertFalse(mm != ALWAYS_EQ) + + call1 = mock.call(mock.MagicMock()) + call2 = mock.call(mock.ANY) + self.assertTrue(call1 == call2) + self.assertFalse(call1 != call2) + self.assertTrue(call2 == call1) + self.assertFalse(call2 != call1) + + self.assertTrue(call1 == ALWAYS_EQ) + self.assertFalse(call1 != ALWAYS_EQ) + self.assertFalse(call1 == 1) + self.assertTrue(call1 != 1) + + + def test_assert_called_with(self): + mock = Mock() + mock() + + # Will raise an exception if it fails + mock.assert_called_with() + self.assertRaises(AssertionError, mock.assert_called_with, 1) + + mock.reset_mock() + self.assertRaises(AssertionError, mock.assert_called_with) + + mock(1, 2, 3, a='fish', b='nothing') + mock.assert_called_with(1, 2, 3, a='fish', b='nothing') + + + def test_assert_called_with_any(self): + m = MagicMock() + m(MagicMock()) + m.assert_called_with(mock.ANY) + + + def test_assert_called_with_function_spec(self): + def f(a, b, c, d=None): pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock.assert_called_with(1, 2, 3) + mock.assert_called_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_with, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_called_with(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + + def test_assert_called_with_method_spec(self): + def _check(mock): + mock(1, b=2, c=3) + mock.assert_called_with(1, 2, 3) + mock.assert_called_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_with, + 1, b=3, c=2) + + mock = Mock(spec=Something().meth) + _check(mock) + mock = Mock(spec=Something.cmeth) + _check(mock) + mock = Mock(spec=Something().cmeth) + _check(mock) + mock = Mock(spec=Something.smeth) + _check(mock) + mock = Mock(spec=Something().smeth) + _check(mock) + + + def test_assert_called_exception_message(self): + msg = "Expected '{0}' to have been called" + with self.assertRaisesRegex(AssertionError, msg.format('mock')): + Mock().assert_called() + with self.assertRaisesRegex(AssertionError, msg.format('test_name')): + Mock(name="test_name").assert_called() + + + def test_assert_called_once_with(self): + mock = Mock() + mock() + + # Will raise an exception if it fails + mock.assert_called_once_with() + + mock() + self.assertRaises(AssertionError, mock.assert_called_once_with) + + mock.reset_mock() + self.assertRaises(AssertionError, mock.assert_called_once_with) + + mock('foo', 'bar', baz=2) + mock.assert_called_once_with('foo', 'bar', baz=2) + + mock.reset_mock() + mock('foo', 'bar', baz=2) + self.assertRaises( + AssertionError, + lambda: mock.assert_called_once_with('bob', 'bar', baz=2) + ) + + def test_assert_called_once_with_call_list(self): + m = Mock() + m(1) + m(2) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1), call(2)]"), + lambda: m.assert_called_once_with(2)) + + + def test_assert_called_once_with_function_spec(self): + def f(a, b, c, d=None): pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock.assert_called_once_with(1, 2, 3) + mock.assert_called_once_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_called_once_with(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + # Mock called more than once => always fails + mock(4, 5, 6) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 1, 2, 3) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 4, 5, 6) + + + def test_attribute_access_returns_mocks(self): + mock = Mock() + something = mock.something + self.assertTrue(is_instance(something, Mock), "attribute isn't a mock") + self.assertEqual(mock.something, something, + "different attributes returned for same name") + + # Usage example + mock = Mock() + mock.something.return_value = 3 + + self.assertEqual(mock.something(), 3, "method returned wrong value") + self.assertTrue(mock.something.called, + "method didn't record being called") + + + def test_attributes_have_name_and_parent_set(self): + mock = Mock() + something = mock.something + + self.assertEqual(something._mock_name, "something", + "attribute name not set correctly") + self.assertEqual(something._mock_parent, mock, + "attribute parent not set correctly") + + + def test_method_calls_recorded(self): + mock = Mock() + mock.something(3, fish=None) + mock.something_else.something(6, cake=sentinel.Cake) + + self.assertEqual(mock.something_else.method_calls, + [("something", (6,), {'cake': sentinel.Cake})], + "method calls not recorded correctly") + self.assertEqual(mock.method_calls, [ + ("something", (3,), {'fish': None}), + ("something_else.something", (6,), {'cake': sentinel.Cake}) + ], + "method calls not recorded correctly") + + + def test_method_calls_compare_easily(self): + mock = Mock() + mock.something() + self.assertEqual(mock.method_calls, [('something',)]) + self.assertEqual(mock.method_calls, [('something', (), {})]) + + mock = Mock() + mock.something('different') + self.assertEqual(mock.method_calls, [('something', ('different',))]) + self.assertEqual(mock.method_calls, + [('something', ('different',), {})]) + + mock = Mock() + mock.something(x=1) + self.assertEqual(mock.method_calls, [('something', {'x': 1})]) + self.assertEqual(mock.method_calls, [('something', (), {'x': 1})]) + + mock = Mock() + mock.something('different', some='more') + self.assertEqual(mock.method_calls, [ + ('something', ('different',), {'some': 'more'}) + ]) + + + def test_only_allowed_methods_exist(self): + for spec in ['something'], ('something',): + for arg in 'spec', 'spec_set': + mock = Mock(**{arg: spec}) + + # this should be allowed + mock.something + self.assertRaisesRegex( + AttributeError, + "Mock object has no attribute 'something_else'", + getattr, mock, 'something_else' + ) + + + def test_from_spec(self): + class Something(object): + x = 3 + __something__ = None + def y(self): pass + + def test_attributes(mock): + # should work + mock.x + mock.y + mock.__something__ + self.assertRaisesRegex( + AttributeError, + "Mock object has no attribute 'z'", + getattr, mock, 'z' + ) + self.assertRaisesRegex( + AttributeError, + "Mock object has no attribute '__foobar__'", + getattr, mock, '__foobar__' + ) + + test_attributes(Mock(spec=Something)) + test_attributes(Mock(spec=Something())) + + + def test_wraps_calls(self): + real = Mock() + + mock = Mock(wraps=real) + self.assertEqual(mock(), real()) + + real.reset_mock() + + mock(1, 2, fish=3) + real.assert_called_with(1, 2, fish=3) + + + def test_wraps_prevents_automatic_creation_of_mocks(self): + class Real(object): + pass + + real = Real() + mock = Mock(wraps=real) + + self.assertRaises(AttributeError, lambda: mock.new_attr()) + + + def test_wraps_call_with_nondefault_return_value(self): + real = Mock() + + mock = Mock(wraps=real) + mock.return_value = 3 + + self.assertEqual(mock(), 3) + self.assertFalse(real.called) + + + def test_wraps_attributes(self): + class Real(object): + attribute = Mock() + + real = Real() + + mock = Mock(wraps=real) + self.assertEqual(mock.attribute(), real.attribute()) + self.assertRaises(AttributeError, lambda: mock.fish) + + self.assertNotEqual(mock.attribute, real.attribute) + result = mock.attribute.frog(1, 2, fish=3) + Real.attribute.frog.assert_called_with(1, 2, fish=3) + self.assertEqual(result, Real.attribute.frog()) + + + def test_customize_wrapped_object_with_side_effect_iterable_with_default(self): + class Real(object): + def method(self): + return sentinel.ORIGINAL_VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.ORIGINAL_VALUE) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_iterable(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_exception(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = RuntimeError + + self.assertRaises(RuntimeError, mock.method) + + + def test_customize_wrapped_object_with_side_effect_function(self): + class Real(object): + def method(self): pass + def side_effect(): + return sentinel.VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = side_effect + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect(self): + # side_effect should always take precedence over return_value. + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + mock.method.return_value = sentinel.WRONG_VALUE + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_return_value_and_side_effect2(self): + # side_effect can return DEFAULT to default to return_value + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = lambda: DEFAULT + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect_default(self): + class Real(object): + def method(self): pass + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + mock.method.return_value = sentinel.RETURN + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.RETURN) + self.assertRaises(StopIteration, mock.method) + + + def test_magic_method_wraps_dict(self): + # bpo-25597: MagicMock with wrap doesn't call wrapped object's + # method for magic methods with default values. + data = {'foo': 'bar'} + + wrapped_dict = MagicMock(wraps=data) + self.assertEqual(wrapped_dict.get('foo'), 'bar') + # Accessing key gives a MagicMock + self.assertIsInstance(wrapped_dict['foo'], MagicMock) + # __contains__ method has a default value of False + self.assertFalse('foo' in wrapped_dict) + + # return_value is non-sentinel and takes precedence over wrapped value. + wrapped_dict.get.return_value = 'return_value' + self.assertEqual(wrapped_dict.get('foo'), 'return_value') + + # return_value is sentinel and hence wrapped value is returned. + wrapped_dict.get.return_value = sentinel.DEFAULT + self.assertEqual(wrapped_dict.get('foo'), 'bar') + + self.assertEqual(wrapped_dict.get('baz'), None) + self.assertIsInstance(wrapped_dict['baz'], MagicMock) + self.assertFalse('bar' in wrapped_dict) + + data['baz'] = 'spam' + self.assertEqual(wrapped_dict.get('baz'), 'spam') + self.assertIsInstance(wrapped_dict['baz'], MagicMock) + self.assertFalse('bar' in wrapped_dict) + + del data['baz'] + self.assertEqual(wrapped_dict.get('baz'), None) + + + def test_magic_method_wraps_class(self): + + class Foo: + + def __getitem__(self, index): + return index + + def __custom_method__(self): + return "foo" + + + klass = MagicMock(wraps=Foo) + obj = klass() + self.assertEqual(obj.__getitem__(2), 2) + self.assertEqual(obj[2], 2) + self.assertEqual(obj.__custom_method__(), "foo") + + + def test_exceptional_side_effect(self): + mock = Mock(side_effect=AttributeError) + self.assertRaises(AttributeError, mock) + + mock = Mock(side_effect=AttributeError('foo')) + self.assertRaises(AttributeError, mock) + + + def test_baseexceptional_side_effect(self): + mock = Mock(side_effect=KeyboardInterrupt) + self.assertRaises(KeyboardInterrupt, mock) + + mock = Mock(side_effect=KeyboardInterrupt('foo')) + self.assertRaises(KeyboardInterrupt, mock) + + + def test_assert_called_with_message(self): + mock = Mock() + self.assertRaisesRegex(AssertionError, 'not called', + mock.assert_called_with) + + + def test_assert_called_once_with_message(self): + mock = Mock(name='geoffrey') + self.assertRaisesRegex(AssertionError, + r"Expected 'geoffrey' to be called once\.", + mock.assert_called_once_with) + + + def test__name__(self): + mock = Mock() + self.assertRaises(AttributeError, lambda: mock.__name__) + + mock.__name__ = 'foo' + self.assertEqual(mock.__name__, 'foo') + + + def test_spec_list_subclass(self): + class Sub(list): + pass + mock = Mock(spec=Sub(['foo'])) + + mock.append(3) + mock.append.assert_called_with(3) + self.assertRaises(AttributeError, getattr, mock, 'foo') + + + def test_spec_class(self): + class X(object): + pass + + mock = Mock(spec=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) + + self.assertIs(mock.__class__, X) + self.assertEqual(Mock().__class__.__name__, 'Mock') + + mock = Mock(spec_set=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec_set=X()) + self.assertIsInstance(mock, X) + + + def test_spec_class_no_object_base(self): + class X: + pass + + mock = Mock(spec=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) + + self.assertIs(mock.__class__, X) + self.assertEqual(Mock().__class__.__name__, 'Mock') + + mock = Mock(spec_set=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec_set=X()) + self.assertIsInstance(mock, X) + + + def test_setting_attribute_with_spec_set(self): + class X(object): + y = 3 + + mock = Mock(spec=X) + mock.x = 'foo' + + mock = Mock(spec_set=X) + def set_attr(): + mock.x = 'foo' + + mock.y = 'foo' + self.assertRaises(AttributeError, set_attr) + + + def test_copy(self): + current = sys.getrecursionlimit() + self.addCleanup(sys.setrecursionlimit, current) + + # can't use sys.maxint as this doesn't exist in Python 3 + sys.setrecursionlimit(int(10e8)) + # this segfaults without the fix in place + copy.copy(Mock()) + + + def test_subclass_with_properties(self): + class SubClass(Mock): + def _get(self): + return 3 + def _set(self, value): + raise NameError('strange error') + some_attribute = property(_get, _set) + + s = SubClass(spec_set=SubClass) + self.assertEqual(s.some_attribute, 3) + + def test(): + s.some_attribute = 3 + self.assertRaises(NameError, test) + + def test(): + s.foo = 'bar' + self.assertRaises(AttributeError, test) + + + def test_setting_call(self): + mock = Mock() + def __call__(self, a): + self._increment_mock_call(a) + return self._mock_call(a) + + type(mock).__call__ = __call__ + mock('one') + mock.assert_called_with('one') + + self.assertRaises(TypeError, mock, 'one', 'two') + + + def test_dir(self): + mock = Mock() + attrs = set(dir(mock)) + type_attrs = set([m for m in dir(Mock) if not m.startswith('_')]) + + # all public attributes from the type are included + self.assertEqual(set(), type_attrs - attrs) + + # creates these attributes + mock.a, mock.b + self.assertIn('a', dir(mock)) + self.assertIn('b', dir(mock)) + + # instance attributes + mock.c = mock.d = None + self.assertIn('c', dir(mock)) + self.assertIn('d', dir(mock)) + + # magic methods + mock.__iter__ = lambda s: iter([]) + self.assertIn('__iter__', dir(mock)) + + + def test_dir_from_spec(self): + mock = Mock(spec=unittest.TestCase) + testcase_attrs = set(dir(unittest.TestCase)) + attrs = set(dir(mock)) + + # all attributes from the spec are included + self.assertEqual(set(), testcase_attrs - attrs) + + # shadow a sys attribute + mock.version = 3 + self.assertEqual(dir(mock).count('version'), 1) + + + def test_filter_dir(self): + patcher = patch.object(mock, 'FILTER_DIR', False) + patcher.start() + try: + attrs = set(dir(Mock())) + type_attrs = set(dir(Mock)) + + # ALL attributes from the type are included + self.assertEqual(set(), type_attrs - attrs) + finally: + patcher.stop() + + + def test_dir_does_not_include_deleted_attributes(self): + mock = Mock() + mock.child.return_value = 1 + + self.assertIn('child', dir(mock)) + del mock.child + self.assertNotIn('child', dir(mock)) + + + def test_configure_mock(self): + mock = Mock(foo='bar') + self.assertEqual(mock.foo, 'bar') + + mock = MagicMock(foo='bar') + self.assertEqual(mock.foo, 'bar') + + kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, + 'foo': MagicMock()} + mock = Mock(**kwargs) + self.assertRaises(KeyError, mock) + self.assertEqual(mock.foo.bar(), 33) + self.assertIsInstance(mock.foo, MagicMock) + + mock = Mock() + mock.configure_mock(**kwargs) + self.assertRaises(KeyError, mock) + self.assertEqual(mock.foo.bar(), 33) + self.assertIsInstance(mock.foo, MagicMock) + + + def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): + # needed because assertRaisesRegex doesn't work easily with newlines + with self.assertRaises(exception) as context: + func(*args, **kwargs) + msg = str(context.exception) + self.assertEqual(msg, message) + + + def test_assert_called_with_failure_message(self): + mock = NonCallableMock() + + actual = 'not called.' + expected = "mock(1, '2', 3, bar='foo')" + message = 'expected call not found.\nExpected: %s\nActual: %s' + self.assertRaisesWithMsg( + AssertionError, message % (expected, actual), + mock.assert_called_with, 1, '2', 3, bar='foo' + ) + + mock.foo(1, '2', 3, foo='foo') + + + asserters = [ + mock.foo.assert_called_with, mock.foo.assert_called_once_with + ] + for meth in asserters: + actual = "foo(1, '2', 3, foo='foo')" + expected = "foo(1, '2', 3, bar='foo')" + message = 'expected call not found.\nExpected: %s\nActual: %s' + self.assertRaisesWithMsg( + AssertionError, message % (expected, actual), + meth, 1, '2', 3, bar='foo' + ) + + # just kwargs + for meth in asserters: + actual = "foo(1, '2', 3, foo='foo')" + expected = "foo(bar='foo')" + message = 'expected call not found.\nExpected: %s\nActual: %s' + self.assertRaisesWithMsg( + AssertionError, message % (expected, actual), + meth, bar='foo' + ) + + # just args + for meth in asserters: + actual = "foo(1, '2', 3, foo='foo')" + expected = "foo(1, 2, 3)" + message = 'expected call not found.\nExpected: %s\nActual: %s' + self.assertRaisesWithMsg( + AssertionError, message % (expected, actual), + meth, 1, 2, 3 + ) + + # empty + for meth in asserters: + actual = "foo(1, '2', 3, foo='foo')" + expected = "foo()" + message = 'expected call not found.\nExpected: %s\nActual: %s' + self.assertRaisesWithMsg( + AssertionError, message % (expected, actual), meth + ) + + + def test_mock_calls(self): + mock = MagicMock() + + # need to do this because MagicMock.mock_calls used to just return + # a MagicMock which also returned a MagicMock when __eq__ was called + self.assertIs(mock.mock_calls == [], True) + + mock = MagicMock() + mock() + expected = [('', (), {})] + self.assertEqual(mock.mock_calls, expected) + + mock.foo() + expected.append(call.foo()) + self.assertEqual(mock.mock_calls, expected) + # intermediate mock_calls work too + self.assertEqual(mock.foo.mock_calls, [('', (), {})]) + + mock = MagicMock() + mock().foo(1, 2, 3, a=4, b=5) + expected = [ + ('', (), {}), ('().foo', (1, 2, 3), dict(a=4, b=5)) + ] + self.assertEqual(mock.mock_calls, expected) + self.assertEqual(mock.return_value.foo.mock_calls, + [('', (1, 2, 3), dict(a=4, b=5))]) + self.assertEqual(mock.return_value.mock_calls, + [('foo', (1, 2, 3), dict(a=4, b=5))]) + + mock = MagicMock() + mock().foo.bar().baz() + expected = [ + ('', (), {}), ('().foo.bar', (), {}), + ('().foo.bar().baz', (), {}) + ] + self.assertEqual(mock.mock_calls, expected) + self.assertEqual(mock().mock_calls, + call.foo.bar().baz().call_list()) + + for kwargs in dict(), dict(name='bar'): + mock = MagicMock(**kwargs) + int(mock.foo) + expected = [('foo.__int__', (), {})] + self.assertEqual(mock.mock_calls, expected) + + mock = MagicMock(**kwargs) + mock.a()() + expected = [('a', (), {}), ('a()', (), {})] + self.assertEqual(mock.mock_calls, expected) + self.assertEqual(mock.a().mock_calls, [call()]) + + mock = MagicMock(**kwargs) + mock(1)(2)(3) + self.assertEqual(mock.mock_calls, call(1)(2)(3).call_list()) + self.assertEqual(mock().mock_calls, call(2)(3).call_list()) + self.assertEqual(mock()().mock_calls, call(3).call_list()) + + mock = MagicMock(**kwargs) + mock(1)(2)(3).a.b.c(4) + self.assertEqual(mock.mock_calls, + call(1)(2)(3).a.b.c(4).call_list()) + self.assertEqual(mock().mock_calls, + call(2)(3).a.b.c(4).call_list()) + self.assertEqual(mock()().mock_calls, + call(3).a.b.c(4).call_list()) + + mock = MagicMock(**kwargs) + int(mock().foo.bar().baz()) + last_call = ('().foo.bar().baz().__int__', (), {}) + self.assertEqual(mock.mock_calls[-1], last_call) + self.assertEqual(mock().mock_calls, + call.foo.bar().baz().__int__().call_list()) + self.assertEqual(mock().foo.bar().mock_calls, + call.baz().__int__().call_list()) + self.assertEqual(mock().foo.bar().baz.mock_calls, + call().__int__().call_list()) + + + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + + def test_mock_call_repr_loop(self): + m = Mock() + m.foo = m + repr(m.foo()) + self.assertRegex(repr(m.foo()), r"") + + + def test_mock_calls_contains(self): + m = Mock() + self.assertFalse([call()] in m.mock_calls) + + + def test_subclassing(self): + class Subclass(Mock): + pass + + mock = Subclass() + self.assertIsInstance(mock.foo, Subclass) + self.assertIsInstance(mock(), Subclass) + + class Subclass(Mock): + def _get_child_mock(self, **kwargs): + return Mock(**kwargs) + + mock = Subclass() + self.assertNotIsInstance(mock.foo, Subclass) + self.assertNotIsInstance(mock(), Subclass) + + + def test_arg_lists(self): + mocks = [ + Mock(), + MagicMock(), + NonCallableMock(), + NonCallableMagicMock() + ] + + def assert_attrs(mock): + names = 'call_args_list', 'method_calls', 'mock_calls' + for name in names: + attr = getattr(mock, name) + self.assertIsInstance(attr, _CallList) + self.assertIsInstance(attr, list) + self.assertEqual(attr, []) + + for mock in mocks: + assert_attrs(mock) + + if callable(mock): + mock() + mock(1, 2) + mock(a=3) + + mock.reset_mock() + assert_attrs(mock) + + mock.foo() + mock.foo.bar(1, a=3) + mock.foo(1).bar().baz(3) + + mock.reset_mock() + assert_attrs(mock) + + + def test_call_args_two_tuple(self): + mock = Mock() + mock(1, a=3) + mock(2, b=4) + + self.assertEqual(len(mock.call_args), 2) + self.assertEqual(mock.call_args.args, (2,)) + self.assertEqual(mock.call_args.kwargs, dict(b=4)) + + expected_list = [((1,), dict(a=3)), ((2,), dict(b=4))] + for expected, call_args in zip(expected_list, mock.call_args_list): + self.assertEqual(len(call_args), 2) + self.assertEqual(expected[0], call_args[0]) + self.assertEqual(expected[1], call_args[1]) + + + def test_side_effect_iterator(self): + mock = Mock(side_effect=iter([1, 2, 3])) + self.assertEqual([mock(), mock(), mock()], [1, 2, 3]) + self.assertRaises(StopIteration, mock) + + mock = MagicMock(side_effect=['a', 'b', 'c']) + self.assertEqual([mock(), mock(), mock()], ['a', 'b', 'c']) + self.assertRaises(StopIteration, mock) + + mock = Mock(side_effect='ghi') + self.assertEqual([mock(), mock(), mock()], ['g', 'h', 'i']) + self.assertRaises(StopIteration, mock) + + class Foo(object): + pass + mock = MagicMock(side_effect=Foo) + self.assertIsInstance(mock(), Foo) + + mock = Mock(side_effect=Iter()) + self.assertEqual([mock(), mock(), mock(), mock()], + ['this', 'is', 'an', 'iter']) + self.assertRaises(StopIteration, mock) + + + def test_side_effect_iterator_exceptions(self): + for Klass in Mock, MagicMock: + iterable = (ValueError, 3, KeyError, 6) + m = Klass(side_effect=iterable) + self.assertRaises(ValueError, m) + self.assertEqual(m(), 3) + self.assertRaises(KeyError, m) + self.assertEqual(m(), 6) + + + def test_side_effect_setting_iterator(self): + mock = Mock() + mock.side_effect = iter([1, 2, 3]) + self.assertEqual([mock(), mock(), mock()], [1, 2, 3]) + self.assertRaises(StopIteration, mock) + side_effect = mock.side_effect + self.assertIsInstance(side_effect, type(iter([]))) + + mock.side_effect = ['a', 'b', 'c'] + self.assertEqual([mock(), mock(), mock()], ['a', 'b', 'c']) + self.assertRaises(StopIteration, mock) + side_effect = mock.side_effect + self.assertIsInstance(side_effect, type(iter([]))) + + this_iter = Iter() + mock.side_effect = this_iter + self.assertEqual([mock(), mock(), mock(), mock()], + ['this', 'is', 'an', 'iter']) + self.assertRaises(StopIteration, mock) + self.assertIs(mock.side_effect, this_iter) + + def test_side_effect_iterator_default(self): + mock = Mock(return_value=2) + mock.side_effect = iter([1, DEFAULT]) + self.assertEqual([mock(), mock()], [1, 2]) + + def test_assert_has_calls_any_order(self): + mock = Mock() + mock(1, 2) + mock(a=3) + mock(3, 4) + mock(b=6) + mock(b=6) + + kalls = [ + call(1, 2), ({'a': 3},), + ((3, 4),), ((), {'a': 3}), + ('', (1, 2)), ('', {'a': 3}), + ('', (1, 2), {}), ('', (), {'a': 3}) + ] + for kall in kalls: + mock.assert_has_calls([kall], any_order=True) + + for kall in call(1, '2'), call(b=3), call(), 3, None, 'foo': + self.assertRaises( + AssertionError, mock.assert_has_calls, + [kall], any_order=True + ) + + kall_lists = [ + [call(1, 2), call(b=6)], + [call(3, 4), call(1, 2)], + [call(b=6), call(b=6)], + ] + + for kall_list in kall_lists: + mock.assert_has_calls(kall_list, any_order=True) + + kall_lists = [ + [call(b=6), call(b=6), call(b=6)], + [call(1, 2), call(1, 2)], + [call(3, 4), call(1, 2), call(5, 7)], + [call(b=6), call(3, 4), call(b=6), call(1, 2), call(b=6)], + ] + for kall_list in kall_lists: + self.assertRaises( + AssertionError, mock.assert_has_calls, + kall_list, any_order=True + ) + + def test_assert_has_calls(self): + kalls1 = [ + call(1, 2), ({'a': 3},), + ((3, 4),), call(b=6), + ('', (1,), {'b': 6}), + ] + kalls2 = [call.foo(), call.bar(1)] + kalls2.extend(call.spam().baz(a=3).call_list()) + kalls2.extend(call.bam(set(), foo={}).fish([1]).call_list()) + + mocks = [] + for mock in Mock(), MagicMock(): + mock(1, 2) + mock(a=3) + mock(3, 4) + mock(b=6) + mock(1, b=6) + mocks.append((mock, kalls1)) + + mock = Mock() + mock.foo() + mock.bar(1) + mock.spam().baz(a=3) + mock.bam(set(), foo={}).fish([1]) + mocks.append((mock, kalls2)) + + for mock, kalls in mocks: + for i in range(len(kalls)): + for step in 1, 2, 3: + these = kalls[i:i+step] + mock.assert_has_calls(these) + + if len(these) > 1: + self.assertRaises( + AssertionError, + mock.assert_has_calls, + list(reversed(these)) + ) + + + def test_assert_has_calls_nested_spec(self): + class Something: + + def __init__(self): pass + def meth(self, a, b, c, d=None): pass + + class Foo: + + def __init__(self, a): pass + def meth1(self, a, b): pass + + mock_class = create_autospec(Something) + + for m in [mock_class, mock_class()]: + m.meth(1, 2, 3, d=1) + m.assert_has_calls([call.meth(1, 2, 3, d=1)]) + m.assert_has_calls([call.meth(1, 2, 3, 1)]) + + mock_class.reset_mock() + + for m in [mock_class, mock_class()]: + self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()]) + m.Foo(1).meth1(1, 2) + m.assert_has_calls([call.Foo(1), call.Foo(1).meth1(1, 2)]) + m.Foo.assert_has_calls([call(1), call().meth1(1, 2)]) + + mock_class.reset_mock() + + invalid_calls = [call.meth(1), + call.non_existent(1), + call.Foo().non_existent(1), + call.Foo().meth(1, 2, 3, 4)] + + for kall in invalid_calls: + self.assertRaises(AssertionError, + mock_class.assert_has_calls, + [kall] + ) + + + def test_assert_has_calls_nested_without_spec(self): + m = MagicMock() + m().foo().bar().baz() + m.one().two().three() + calls = call.one().two().three().call_list() + m.assert_has_calls(calls) + + + def test_assert_has_calls_with_function_spec(self): + def f(a, b, c, d=None): pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock(4, 5, c=6, d=7) + mock(10, 11, c=12) + calls = [ + ('', (1, 2, 3), {}), + ('', (4, 5, 6), {'d': 7}), + ((10, 11, 12), {}), + ] + mock.assert_has_calls(calls) + mock.assert_has_calls(calls, any_order=True) + mock.assert_has_calls(calls[1:]) + mock.assert_has_calls(calls[1:], any_order=True) + mock.assert_has_calls(calls[:-1]) + mock.assert_has_calls(calls[:-1], any_order=True) + # Reversed order + calls = list(reversed(calls)) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls) + mock.assert_has_calls(calls, any_order=True) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls[1:]) + mock.assert_has_calls(calls[1:], any_order=True) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls[:-1]) + mock.assert_has_calls(calls[:-1], any_order=True) + + def test_assert_has_calls_not_matching_spec_error(self): + def f(x=None): pass + + mock = Mock(spec=f) + mock(1) + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape('Calls not found.\n' + 'Expected: [call()]\n' + 'Actual: [call(1)]'))) as cm: + mock.assert_has_calls([call()]) + self.assertIsNone(cm.exception.__cause__) + + + with self.assertRaisesRegex( + AssertionError, + '^{}$'.format( + re.escape( + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments')]\n" + "Expected: [call(), call(1, 2)]\n" + 'Actual: [call(1)]'))) as cm: + mock.assert_has_calls([call(), call(1, 2)]) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + def test_assert_any_call(self): + mock = Mock() + mock(1, 2) + mock(a=3) + mock(1, b=6) + + mock.assert_any_call(1, 2) + mock.assert_any_call(a=3) + mock.assert_any_call(1, b=6) + + self.assertRaises( + AssertionError, + mock.assert_any_call + ) + self.assertRaises( + AssertionError, + mock.assert_any_call, + 1, 3 + ) + self.assertRaises( + AssertionError, + mock.assert_any_call, + a=4 + ) + + + def test_assert_any_call_with_function_spec(self): + def f(a, b, c, d=None): pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock(4, 5, c=6, d=7) + mock.assert_any_call(1, 2, 3) + mock.assert_any_call(a=1, b=2, c=3) + mock.assert_any_call(4, 5, 6, 7) + mock.assert_any_call(a=4, b=5, c=6, d=7) + self.assertRaises(AssertionError, mock.assert_any_call, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_any_call(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + + def test_mock_calls_create_autospec(self): + def f(a, b): pass + obj = Iter() + obj.f = f + + funcs = [ + create_autospec(f), + create_autospec(obj).f + ] + for func in funcs: + func(1, 2) + func(3, 4) + + self.assertEqual( + func.mock_calls, [call(1, 2), call(3, 4)] + ) + + #Issue21222 + def test_create_autospec_with_name(self): + m = mock.create_autospec(object(), name='sweet_func') + self.assertIn('sweet_func', repr(m)) + + #Issue23078 + def test_create_autospec_classmethod_and_staticmethod(self): + class TestClass: + @classmethod + def class_method(cls): pass + + @staticmethod + def static_method(): pass + for method in ('class_method', 'static_method'): + with self.subTest(method=method): + mock_method = mock.create_autospec(getattr(TestClass, method)) + mock_method() + mock_method.assert_called_once_with() + self.assertRaises(TypeError, mock_method, 'extra_arg') + + #Issue21238 + def test_mock_unsafe(self): + m = Mock() + msg = "Attributes cannot start with 'assert' or 'assret'" + with self.assertRaisesRegex(AttributeError, msg): + m.assert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.assret_foo_call() + m = Mock(unsafe=True) + m.assert_foo_call() + m.assret_foo_call() + + #Issue21262 + def test_assert_not_called(self): + m = Mock() + m.hello.assert_not_called() + m.hello() + with self.assertRaises(AssertionError): + m.hello.assert_not_called() + + def test_assert_not_called_message(self): + m = Mock() + m(1, 2) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2)]"), + m.assert_not_called) + + def test_assert_called(self): + m = Mock() + with self.assertRaises(AssertionError): + m.hello.assert_called() + m.hello() + m.hello.assert_called() + + m.hello() + m.hello.assert_called() + + def test_assert_called_once(self): + m = Mock() + with self.assertRaises(AssertionError): + m.hello.assert_called_once() + m.hello() + m.hello.assert_called_once() + + m.hello() + with self.assertRaises(AssertionError): + m.hello.assert_called_once() + + def test_assert_called_once_message(self): + m = Mock() + m(1, 2) + m(3) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2), call(3)]"), + m.assert_called_once) + + def test_assert_called_once_message_not_called(self): + m = Mock() + with self.assertRaises(AssertionError) as e: + m.assert_called_once() + self.assertNotIn("Calls:", str(e.exception)) + + #Issue37212 printout of keyword args now preserves the original order + def test_ordered_call_signature(self): + m = Mock() + m.hello(name='hello', daddy='hero') + text = "call(name='hello', daddy='hero')" + self.assertEqual(repr(m.hello.call_args), text) + + #Issue21270 overrides tuple methods for mock.call objects + def test_override_tuple_methods(self): + c = call.count() + i = call.index(132,'hello') + m = Mock() + m.count() + m.index(132,"hello") + self.assertEqual(m.method_calls[0], c) + self.assertEqual(m.method_calls[1], i) + + def test_reset_return_sideeffect(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True, side_effect=True) + self.assertIsInstance(m.return_value, Mock) + self.assertEqual(m.side_effect, None) + + def test_reset_return(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True) + self.assertIsInstance(m.return_value, Mock) + self.assertNotEqual(m.side_effect, None) + + def test_reset_sideeffect(self): + m = Mock(return_value=10, side_effect=[2, 3]) + m.reset_mock(side_effect=True) + self.assertEqual(m.return_value, 10) + self.assertEqual(m.side_effect, None) + + def test_reset_return_with_children(self): + m = MagicMock(f=MagicMock(return_value=1)) + self.assertEqual(m.f(), 1) + m.reset_mock(return_value=True) + self.assertNotEqual(m.f(), 1) + + def test_reset_return_with_children_side_effect(self): + m = MagicMock(f=MagicMock(side_effect=[2, 3])) + self.assertNotEqual(m.f.side_effect, None) + m.reset_mock(side_effect=True) + self.assertEqual(m.f.side_effect, None) + + def test_mock_add_spec(self): + class _One(object): + one = 1 + class _Two(object): + two = 2 + class Anything(object): + one = two = three = 'four' + + klasses = [ + Mock, MagicMock, NonCallableMock, NonCallableMagicMock + ] + for Klass in list(klasses): + klasses.append(lambda K=Klass: K(spec=Anything)) + klasses.append(lambda K=Klass: K(spec_set=Anything)) + + for Klass in klasses: + for kwargs in dict(), dict(spec_set=True): + mock = Klass() + #no error + mock.one, mock.two, mock.three + + for One, Two in [(_One, _Two), (['one'], ['two'])]: + for kwargs in dict(), dict(spec_set=True): + mock.mock_add_spec(One, **kwargs) + + mock.one + self.assertRaises( + AttributeError, getattr, mock, 'two' + ) + self.assertRaises( + AttributeError, getattr, mock, 'three' + ) + if 'spec_set' in kwargs: + self.assertRaises( + AttributeError, setattr, mock, 'three', None + ) + + mock.mock_add_spec(Two, **kwargs) + self.assertRaises( + AttributeError, getattr, mock, 'one' + ) + mock.two + self.assertRaises( + AttributeError, getattr, mock, 'three' + ) + if 'spec_set' in kwargs: + self.assertRaises( + AttributeError, setattr, mock, 'three', None + ) + # note that creating a mock, setting an instance attribute, and + # *then* setting a spec doesn't work. Not the intended use case + + + def test_mock_add_spec_magic_methods(self): + for Klass in MagicMock, NonCallableMagicMock: + mock = Klass() + int(mock) + + mock.mock_add_spec(object) + self.assertRaises(TypeError, int, mock) + + mock = Klass() + mock['foo'] + mock.__int__.return_value =4 + + mock.mock_add_spec(int) + self.assertEqual(int(mock), 4) + self.assertRaises(TypeError, lambda: mock['foo']) + + + def test_adding_child_mock(self): + for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, + AsyncMock): + mock = Klass() + + mock.foo = Mock() + mock.foo() + + self.assertEqual(mock.method_calls, [call.foo()]) + self.assertEqual(mock.mock_calls, [call.foo()]) + + mock = Klass() + mock.bar = Mock(name='name') + mock.bar() + self.assertEqual(mock.method_calls, []) + self.assertEqual(mock.mock_calls, []) + + # mock with an existing _new_parent but no name + mock = Klass() + mock.baz = MagicMock()() + mock.baz() + self.assertEqual(mock.method_calls, []) + self.assertEqual(mock.mock_calls, []) + + + def test_adding_return_value_mock(self): + for Klass in Mock, MagicMock: + mock = Klass() + mock.return_value = MagicMock() + + mock()() + self.assertEqual(mock.mock_calls, [call(), call()()]) + + + def test_manager_mock(self): + class Foo(object): + one = 'one' + two = 'two' + manager = Mock() + p1 = patch.object(Foo, 'one') + p2 = patch.object(Foo, 'two') + + mock_one = p1.start() + self.addCleanup(p1.stop) + mock_two = p2.start() + self.addCleanup(p2.stop) + + manager.attach_mock(mock_one, 'one') + manager.attach_mock(mock_two, 'two') + + Foo.two() + Foo.one() + + self.assertEqual(manager.mock_calls, [call.two(), call.one()]) + + + def test_magic_methods_mock_calls(self): + for Klass in Mock, MagicMock: + m = Klass() + m.__int__ = Mock(return_value=3) + m.__float__ = MagicMock(return_value=3.0) + int(m) + float(m) + + self.assertEqual(m.mock_calls, [call.__int__(), call.__float__()]) + self.assertEqual(m.method_calls, []) + + def test_mock_open_reuse_issue_21750(self): + mocked_open = mock.mock_open(read_data='data') + f1 = mocked_open('a-name') + f1_data = f1.read() + f2 = mocked_open('another-name') + f2_data = f2.read() + self.assertEqual(f1_data, f2_data) + + def test_mock_open_dunder_iter_issue(self): + # Test dunder_iter method generates the expected result and + # consumes the iterator. + mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') + f1 = mocked_open('a-name') + lines = [line for line in f1] + self.assertEqual(lines[0], 'Remarkable\n') + self.assertEqual(lines[1], 'Norwegian Blue') + self.assertEqual(list(f1), []) + + def test_mock_open_using_next(self): + mocked_open = mock.mock_open(read_data='1st line\n2nd line\n3rd line') + f1 = mocked_open('a-name') + line1 = next(f1) + line2 = f1.__next__() + lines = [line for line in f1] + self.assertEqual(line1, '1st line\n') + self.assertEqual(line2, '2nd line\n') + self.assertEqual(lines[0], '3rd line') + self.assertEqual(list(f1), []) + with self.assertRaises(StopIteration): + next(f1) + + def test_mock_open_next_with_readline_with_return_value(self): + mopen = mock.mock_open(read_data='foo\nbarn') + mopen.return_value.readline.return_value = 'abc' + self.assertEqual('abc', next(mopen())) + + def test_mock_open_write(self): + # Test exception in file writing write() + mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) + with mock.patch('tempfile.NamedTemporaryFile', mock_namedtemp): + mock_filehandle = mock_namedtemp.return_value + mock_write = mock_filehandle.write + mock_write.side_effect = OSError('Test 2 Error') + def attempt(): + tempfile.NamedTemporaryFile().write('asd') + self.assertRaises(OSError, attempt) + + def test_mock_open_alter_readline(self): + mopen = mock.mock_open(read_data='foo\nbarn') + mopen.return_value.readline.side_effect = lambda *args:'abc' + first = mopen().readline() + second = mopen().readline() + self.assertEqual('abc', first) + self.assertEqual('abc', second) + + def test_mock_open_after_eof(self): + # read, readline and readlines should work after end of file. + _open = mock.mock_open(read_data='foo') + h = _open('bar') + h.read() + self.assertEqual('', h.read()) + self.assertEqual('', h.read()) + self.assertEqual('', h.readline()) + self.assertEqual('', h.readline()) + self.assertEqual([], h.readlines()) + self.assertEqual([], h.readlines()) + + def test_mock_parents(self): + for Klass in Mock, MagicMock: + m = Klass() + original_repr = repr(m) + m.return_value = m + self.assertIs(m(), m) + self.assertEqual(repr(m), original_repr) + + m.reset_mock() + self.assertIs(m(), m) + self.assertEqual(repr(m), original_repr) + + m = Klass() + m.b = m.a + self.assertIn("name='mock.a'", repr(m.b)) + self.assertIn("name='mock.a'", repr(m.a)) + m.reset_mock() + self.assertIn("name='mock.a'", repr(m.b)) + self.assertIn("name='mock.a'", repr(m.a)) + + m = Klass() + original_repr = repr(m) + m.a = m() + m.a.return_value = m + + self.assertEqual(repr(m), original_repr) + self.assertEqual(repr(m.a()), original_repr) + + + def test_attach_mock(self): + classes = Mock, MagicMock, NonCallableMagicMock, NonCallableMock + for Klass in classes: + for Klass2 in classes: + m = Klass() + + m2 = Klass2(name='foo') + m.attach_mock(m2, 'bar') + + self.assertIs(m.bar, m2) + self.assertIn("name='mock.bar'", repr(m2)) + + m.bar.baz(1) + self.assertEqual(m.mock_calls, [call.bar.baz(1)]) + self.assertEqual(m.method_calls, [call.bar.baz(1)]) + + + def test_attach_mock_return_value(self): + classes = Mock, MagicMock, NonCallableMagicMock, NonCallableMock + for Klass in Mock, MagicMock: + for Klass2 in classes: + m = Klass() + + m2 = Klass2(name='foo') + m.attach_mock(m2, 'return_value') + + self.assertIs(m(), m2) + self.assertIn("name='mock()'", repr(m2)) + + m2.foo() + self.assertEqual(m.mock_calls, call().foo().call_list()) + + + def test_attach_mock_patch_autospec(self): + parent = Mock() + + with mock.patch(f'{__name__}.something', autospec=True) as mock_func: + self.assertEqual(mock_func.mock._extract_mock_name(), 'something') + parent.attach_mock(mock_func, 'child') + parent.child(1) + something(2) + mock_func(3) + + parent_calls = [call.child(1), call.child(2), call.child(3)] + child_calls = [call(1), call(2), call(3)] + self.assertEqual(parent.mock_calls, parent_calls) + self.assertEqual(parent.child.mock_calls, child_calls) + self.assertEqual(something.mock_calls, child_calls) + self.assertEqual(mock_func.mock_calls, child_calls) + self.assertIn('mock.child', repr(parent.child.mock)) + self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child') + + + def test_attach_mock_patch_autospec_signature(self): + with mock.patch(f'{__name__}.Something.meth', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_meth') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_meth(mock.ANY, 1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + mocked.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + + with mock.patch(f'{__name__}.something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_func') + something(1) + manager.assert_has_calls([call.attach_func(1)]) + something.assert_has_calls([call(1)]) + mocked.assert_has_calls([call(1)]) + + with mock.patch(f'{__name__}.Something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_obj') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_obj(), + call.attach_obj().meth(1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(1, 2, 3, d=4)]) + mocked.assert_has_calls([call(), call().meth(1, 2, 3, d=4)]) + + + def test_attribute_deletion(self): + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + self.assertTrue(hasattr(mock, 'm')) + + del mock.m + self.assertFalse(hasattr(mock, 'm')) + + del mock.f + self.assertFalse(hasattr(mock, 'f')) + self.assertRaises(AttributeError, getattr, mock, 'f') + + + def test_mock_does_not_raise_on_repeated_attribute_deletion(self): + # bpo-20239: Assigning and deleting twice an attribute raises. + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + mock.foo = 3 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 3) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + mock.foo = 4 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 4) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + + def test_mock_raises_when_deleting_nonexistent_attribute(self): + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + del mock.foo + with self.assertRaises(AttributeError): + del mock.foo + + + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + + def test_class_assignable(self): + for mock in Mock(), MagicMock(): + self.assertNotIsInstance(mock, int) + + mock.__class__ = int + self.assertIsInstance(mock, int) + mock.foo + + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) + + + def test_parent_propagation_with_create_autospec(self): + + def foo(a, b): pass + + mock = Mock() + mock.child = create_autospec(foo) + mock.child(1, 2) + + self.assertRaises(TypeError, mock.child, 1) + self.assertEqual(mock.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(mock.child.mock)) + + def test_parent_propagation_with_autospec_attach_mock(self): + + def foo(a, b): pass + + parent = Mock() + parent.attach_mock(create_autospec(foo, name='bar'), 'child') + parent.child(1, 2) + + self.assertRaises(TypeError, parent.child, 1) + self.assertEqual(parent.child.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(parent.child.mock)) + + + def test_isinstance_under_settrace(self): + # bpo-36593 : __class__ is not set for a class that has __class__ + # property defined when it's used with sys.settrace(trace) set. + # Delete the module to force reimport with tracing function set + # restore the old reference later since there are other tests that are + # dependent on unittest.mock.patch. In testpatch.PatchTest + # test_patch_dict_test_prefix and test_patch_test_prefix not restoring + # causes the objects patched to go out of sync + + old_patch = unittest.mock.patch + + # Directly using __setattr__ on unittest.mock causes current imported + # reference to be updated. Use a lambda so that during cleanup the + # re-imported new reference is updated. + self.addCleanup(lambda patch: setattr(unittest.mock, 'patch', patch), + old_patch) + + with patch.dict('sys.modules'): + del sys.modules['unittest.mock'] + + # This trace will stop coverage being measured ;-) + def trace(frame, event, arg): # pragma: no cover + return trace + + self.addCleanup(sys.settrace, sys.gettrace()) + sys.settrace(trace) + + from unittest.mock import ( + Mock, MagicMock, NonCallableMock, NonCallableMagicMock + ) + + mocks = [ + Mock, MagicMock, NonCallableMock, NonCallableMagicMock, AsyncMock + ] + + for mock in mocks: + obj = mock(spec=Something) + self.assertIsInstance(obj, Something) + + def test_bool_not_called_when_passing_spec_arg(self): + class Something: + def __init__(self): + self.obj_with_bool_func = unittest.mock.MagicMock() + + obj = Something() + with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass + + self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py b/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py new file mode 100644 index 0000000000..d8c1515f83 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py @@ -0,0 +1,1947 @@ +# Copyright (C) 2007-2012 Michael Foord & the mock team +# E-mail: fuzzyman AT voidspace DOT org DOT uk +# http://www.voidspace.org.uk/python/mock/ + +import os +import sys +from collections import OrderedDict + +import unittest +from unittest.test.testmock import support +from unittest.test.testmock.support import SomeClass, is_instance + +from test.test_importlib.util import uncache +from unittest.mock import ( + NonCallableMock, CallableMixin, sentinel, + MagicMock, Mock, NonCallableMagicMock, patch, _patch, + DEFAULT, call, _get_target +) + + +builtin_string = 'builtins' + +PTModule = sys.modules[__name__] +MODNAME = '%s.PTModule' % __name__ + + +def _get_proxy(obj, get_only=True): + class Proxy(object): + def __getattr__(self, name): + return getattr(obj, name) + if not get_only: + def __setattr__(self, name, value): + setattr(obj, name, value) + def __delattr__(self, name): + delattr(obj, name) + Proxy.__setattr__ = __setattr__ + Proxy.__delattr__ = __delattr__ + return Proxy() + + +# for use in the test +something = sentinel.Something +something_else = sentinel.SomethingElse + + +class Foo(object): + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass + foo = 'bar' + + @staticmethod + def static_method(): pass + + @classmethod + def class_method(cls): pass + + class Bar(object): + def a(self): pass + +foo_name = '%s.Foo' % __name__ + + +def function(a, b=Foo): pass + + +class Container(object): + def __init__(self): + self.values = {} + + def __getitem__(self, name): + return self.values[name] + + def __setitem__(self, name, value): + self.values[name] = value + + def __delitem__(self, name): + del self.values[name] + + def __iter__(self): + return iter(self.values) + + + +class PatchTest(unittest.TestCase): + + def assertNotCallable(self, obj, magic=True): + MockClass = NonCallableMagicMock + if not magic: + MockClass = NonCallableMock + + self.assertRaises(TypeError, obj) + self.assertTrue(is_instance(obj, MockClass)) + self.assertFalse(is_instance(obj, CallableMixin)) + + + def test_single_patchobject(self): + class Something(object): + attribute = sentinel.Original + + @patch.object(Something, 'attribute', sentinel.Patched) + def test(): + self.assertEqual(Something.attribute, sentinel.Patched, "unpatched") + + test() + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + + def test_patchobject_with_string_as_target(self): + msg = "'Something' must be the actual object to be patched, not a str" + with self.assertRaisesRegex(TypeError, msg): + patch.object('Something', 'do_something') + + def test_patchobject_with_none(self): + class Something(object): + attribute = sentinel.Original + + @patch.object(Something, 'attribute', None) + def test(): + self.assertIsNone(Something.attribute, "unpatched") + + test() + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + + + def test_multiple_patchobject(self): + class Something(object): + attribute = sentinel.Original + next_attribute = sentinel.Original2 + + @patch.object(Something, 'attribute', sentinel.Patched) + @patch.object(Something, 'next_attribute', sentinel.Patched2) + def test(): + self.assertEqual(Something.attribute, sentinel.Patched, + "unpatched") + self.assertEqual(Something.next_attribute, sentinel.Patched2, + "unpatched") + + test() + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + self.assertEqual(Something.next_attribute, sentinel.Original2, + "patch not restored") + + + def test_object_lookup_is_quite_lazy(self): + global something + original = something + @patch('%s.something' % __name__, sentinel.Something2) + def test(): + pass + + try: + something = sentinel.replacement_value + test() + self.assertEqual(something, sentinel.replacement_value) + finally: + something = original + + + def test_patch(self): + @patch('%s.something' % __name__, sentinel.Something2) + def test(): + self.assertEqual(PTModule.something, sentinel.Something2, + "unpatched") + + test() + self.assertEqual(PTModule.something, sentinel.Something, + "patch not restored") + + @patch('%s.something' % __name__, sentinel.Something2) + @patch('%s.something_else' % __name__, sentinel.SomethingElse) + def test(): + self.assertEqual(PTModule.something, sentinel.Something2, + "unpatched") + self.assertEqual(PTModule.something_else, sentinel.SomethingElse, + "unpatched") + + self.assertEqual(PTModule.something, sentinel.Something, + "patch not restored") + self.assertEqual(PTModule.something_else, sentinel.SomethingElse, + "patch not restored") + + # Test the patching and restoring works a second time + test() + + self.assertEqual(PTModule.something, sentinel.Something, + "patch not restored") + self.assertEqual(PTModule.something_else, sentinel.SomethingElse, + "patch not restored") + + mock = Mock() + mock.return_value = sentinel.Handle + @patch('%s.open' % builtin_string, mock) + def test(): + self.assertEqual(open('filename', 'r'), sentinel.Handle, + "open not patched") + test() + test() + + self.assertNotEqual(open, mock, "patch not restored") + + + def test_patch_class_attribute(self): + @patch('%s.SomeClass.class_attribute' % __name__, + sentinel.ClassAttribute) + def test(): + self.assertEqual(PTModule.SomeClass.class_attribute, + sentinel.ClassAttribute, "unpatched") + test() + + self.assertIsNone(PTModule.SomeClass.class_attribute, + "patch not restored") + + + def test_patchobject_with_default_mock(self): + class Test(object): + something = sentinel.Original + something2 = sentinel.Original2 + + @patch.object(Test, 'something') + def test(mock): + self.assertEqual(mock, Test.something, + "Mock not passed into test function") + self.assertIsInstance(mock, MagicMock, + "patch with two arguments did not create a mock") + + test() + + @patch.object(Test, 'something') + @patch.object(Test, 'something2') + def test(this1, this2, mock1, mock2): + self.assertEqual(this1, sentinel.this1, + "Patched function didn't receive initial argument") + self.assertEqual(this2, sentinel.this2, + "Patched function didn't receive second argument") + self.assertEqual(mock1, Test.something2, + "Mock not passed into test function") + self.assertEqual(mock2, Test.something, + "Second Mock not passed into test function") + self.assertIsInstance(mock2, MagicMock, + "patch with two arguments did not create a mock") + self.assertIsInstance(mock2, MagicMock, + "patch with two arguments did not create a mock") + + # A hack to test that new mocks are passed the second time + self.assertNotEqual(outerMock1, mock1, "unexpected value for mock1") + self.assertNotEqual(outerMock2, mock2, "unexpected value for mock1") + return mock1, mock2 + + outerMock1 = outerMock2 = None + outerMock1, outerMock2 = test(sentinel.this1, sentinel.this2) + + # Test that executing a second time creates new mocks + test(sentinel.this1, sentinel.this2) + + + def test_patch_with_spec(self): + @patch('%s.SomeClass' % __name__, spec=SomeClass) + def test(MockSomeClass): + self.assertEqual(SomeClass, MockSomeClass) + self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) + self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) + + test() + + + def test_patchobject_with_spec(self): + @patch.object(SomeClass, 'class_attribute', spec=SomeClass) + def test(MockAttribute): + self.assertEqual(SomeClass.class_attribute, MockAttribute) + self.assertTrue(is_instance(SomeClass.class_attribute.wibble, + MagicMock)) + self.assertRaises(AttributeError, + lambda: SomeClass.class_attribute.not_wibble) + + test() + + + def test_patch_with_spec_as_list(self): + @patch('%s.SomeClass' % __name__, spec=['wibble']) + def test(MockSomeClass): + self.assertEqual(SomeClass, MockSomeClass) + self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) + self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) + + test() + + + def test_patchobject_with_spec_as_list(self): + @patch.object(SomeClass, 'class_attribute', spec=['wibble']) + def test(MockAttribute): + self.assertEqual(SomeClass.class_attribute, MockAttribute) + self.assertTrue(is_instance(SomeClass.class_attribute.wibble, + MagicMock)) + self.assertRaises(AttributeError, + lambda: SomeClass.class_attribute.not_wibble) + + test() + + + def test_nested_patch_with_spec_as_list(self): + # regression test for nested decorators + @patch('%s.open' % builtin_string) + @patch('%s.SomeClass' % __name__, spec=['wibble']) + def test(MockSomeClass, MockOpen): + self.assertEqual(SomeClass, MockSomeClass) + self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) + self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) + test() + + + def test_patch_with_spec_as_boolean(self): + @patch('%s.SomeClass' % __name__, spec=True) + def test(MockSomeClass): + self.assertEqual(SomeClass, MockSomeClass) + # Should not raise attribute error + MockSomeClass.wibble + + self.assertRaises(AttributeError, lambda: MockSomeClass.not_wibble) + + test() + + + def test_patch_object_with_spec_as_boolean(self): + @patch.object(PTModule, 'SomeClass', spec=True) + def test(MockSomeClass): + self.assertEqual(SomeClass, MockSomeClass) + # Should not raise attribute error + MockSomeClass.wibble + + self.assertRaises(AttributeError, lambda: MockSomeClass.not_wibble) + + test() + + + def test_patch_class_acts_with_spec_is_inherited(self): + @patch('%s.SomeClass' % __name__, spec=True) + def test(MockSomeClass): + self.assertTrue(is_instance(MockSomeClass, MagicMock)) + instance = MockSomeClass() + self.assertNotCallable(instance) + # Should not raise attribute error + instance.wibble + + self.assertRaises(AttributeError, lambda: instance.not_wibble) + + test() + + + def test_patch_with_create_mocks_non_existent_attributes(self): + @patch('%s.frooble' % builtin_string, sentinel.Frooble, create=True) + def test(): + self.assertEqual(frooble, sentinel.Frooble) + + test() + self.assertRaises(NameError, lambda: frooble) + + + def test_patchobject_with_create_mocks_non_existent_attributes(self): + @patch.object(SomeClass, 'frooble', sentinel.Frooble, create=True) + def test(): + self.assertEqual(SomeClass.frooble, sentinel.Frooble) + + test() + self.assertFalse(hasattr(SomeClass, 'frooble')) + + + def test_patch_wont_create_by_default(self): + with self.assertRaises(AttributeError): + @patch('%s.frooble' % builtin_string, sentinel.Frooble) + def test(): pass + + test() + self.assertRaises(NameError, lambda: frooble) + + + def test_patchobject_wont_create_by_default(self): + with self.assertRaises(AttributeError): + @patch.object(SomeClass, 'ord', sentinel.Frooble) + def test(): pass + test() + self.assertFalse(hasattr(SomeClass, 'ord')) + + + def test_patch_builtins_without_create(self): + @patch(__name__+'.ord') + def test_ord(mock_ord): + mock_ord.return_value = 101 + return ord('c') + + @patch(__name__+'.open') + def test_open(mock_open): + m = mock_open.return_value + m.read.return_value = 'abcd' + + fobj = open('doesnotexists.txt') + data = fobj.read() + fobj.close() + return data + + self.assertEqual(test_ord(), 101) + self.assertEqual(test_open(), 'abcd') + + + def test_patch_with_static_methods(self): + class Foo(object): + @staticmethod + def woot(): + return sentinel.Static + + @patch.object(Foo, 'woot', staticmethod(lambda: sentinel.Patched)) + def anonymous(): + self.assertEqual(Foo.woot(), sentinel.Patched) + anonymous() + + self.assertEqual(Foo.woot(), sentinel.Static) + + + def test_patch_local(self): + foo = sentinel.Foo + @patch.object(sentinel, 'Foo', 'Foo') + def anonymous(): + self.assertEqual(sentinel.Foo, 'Foo') + anonymous() + + self.assertEqual(sentinel.Foo, foo) + + + def test_patch_slots(self): + class Foo(object): + __slots__ = ('Foo',) + + foo = Foo() + foo.Foo = sentinel.Foo + + @patch.object(foo, 'Foo', 'Foo') + def anonymous(): + self.assertEqual(foo.Foo, 'Foo') + anonymous() + + self.assertEqual(foo.Foo, sentinel.Foo) + + + def test_patchobject_class_decorator(self): + class Something(object): + attribute = sentinel.Original + + class Foo(object): + def test_method(other_self): + self.assertEqual(Something.attribute, sentinel.Patched, + "unpatched") + def not_test_method(other_self): + self.assertEqual(Something.attribute, sentinel.Original, + "non-test method patched") + + Foo = patch.object(Something, 'attribute', sentinel.Patched)(Foo) + + f = Foo() + f.test_method() + f.not_test_method() + + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + + + def test_patch_class_decorator(self): + class Something(object): + attribute = sentinel.Original + + class Foo(object): + + test_class_attr = 'whatever' + + def test_method(other_self, mock_something): + self.assertEqual(PTModule.something, mock_something, + "unpatched") + def not_test_method(other_self): + self.assertEqual(PTModule.something, sentinel.Something, + "non-test method patched") + Foo = patch('%s.something' % __name__)(Foo) + + f = Foo() + f.test_method() + f.not_test_method() + + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + self.assertEqual(PTModule.something, sentinel.Something, + "patch not restored") + + + def test_patchobject_twice(self): + class Something(object): + attribute = sentinel.Original + next_attribute = sentinel.Original2 + + @patch.object(Something, 'attribute', sentinel.Patched) + @patch.object(Something, 'attribute', sentinel.Patched) + def test(): + self.assertEqual(Something.attribute, sentinel.Patched, "unpatched") + + test() + + self.assertEqual(Something.attribute, sentinel.Original, + "patch not restored") + + + def test_patch_dict(self): + foo = {'initial': object(), 'other': 'something'} + original = foo.copy() + + @patch.dict(foo) + def test(): + foo['a'] = 3 + del foo['initial'] + foo['other'] = 'something else' + + test() + + self.assertEqual(foo, original) + + @patch.dict(foo, {'a': 'b'}) + def test(): + self.assertEqual(len(foo), 3) + self.assertEqual(foo['a'], 'b') + + test() + + self.assertEqual(foo, original) + + @patch.dict(foo, [('a', 'b')]) + def test(): + self.assertEqual(len(foo), 3) + self.assertEqual(foo['a'], 'b') + + test() + + self.assertEqual(foo, original) + + + def test_patch_dict_with_container_object(self): + foo = Container() + foo['initial'] = object() + foo['other'] = 'something' + + original = foo.values.copy() + + @patch.dict(foo) + def test(): + foo['a'] = 3 + del foo['initial'] + foo['other'] = 'something else' + + test() + + self.assertEqual(foo.values, original) + + @patch.dict(foo, {'a': 'b'}) + def test(): + self.assertEqual(len(foo.values), 3) + self.assertEqual(foo['a'], 'b') + + test() + + self.assertEqual(foo.values, original) + + + def test_patch_dict_with_clear(self): + foo = {'initial': object(), 'other': 'something'} + original = foo.copy() + + @patch.dict(foo, clear=True) + def test(): + self.assertEqual(foo, {}) + foo['a'] = 3 + foo['other'] = 'something else' + + test() + + self.assertEqual(foo, original) + + @patch.dict(foo, {'a': 'b'}, clear=True) + def test(): + self.assertEqual(foo, {'a': 'b'}) + + test() + + self.assertEqual(foo, original) + + @patch.dict(foo, [('a', 'b')], clear=True) + def test(): + self.assertEqual(foo, {'a': 'b'}) + + test() + + self.assertEqual(foo, original) + + + def test_patch_dict_with_container_object_and_clear(self): + foo = Container() + foo['initial'] = object() + foo['other'] = 'something' + + original = foo.values.copy() + + @patch.dict(foo, clear=True) + def test(): + self.assertEqual(foo.values, {}) + foo['a'] = 3 + foo['other'] = 'something else' + + test() + + self.assertEqual(foo.values, original) + + @patch.dict(foo, {'a': 'b'}, clear=True) + def test(): + self.assertEqual(foo.values, {'a': 'b'}) + + test() + + self.assertEqual(foo.values, original) + + + def test_patch_dict_as_context_manager(self): + foo = {'a': 'b'} + with patch.dict(foo, a='c') as patched: + self.assertEqual(patched, {'a': 'c'}) + self.assertEqual(foo, {'a': 'b'}) + + + def test_name_preserved(self): + foo = {} + + @patch('%s.SomeClass' % __name__, object()) + @patch('%s.SomeClass' % __name__, object(), autospec=True) + @patch.object(SomeClass, object()) + @patch.dict(foo) + def some_name(): pass + + self.assertEqual(some_name.__name__, 'some_name') + + + def test_patch_with_exception(self): + foo = {} + + @patch.dict(foo, {'a': 'b'}) + def test(): + raise NameError('Konrad') + + with self.assertRaises(NameError): + test() + + self.assertEqual(foo, {}) + + + def test_patch_dict_with_string(self): + @patch.dict('os.environ', {'konrad_delong': 'some value'}) + def test(): + self.assertIn('konrad_delong', os.environ) + + test() + + + def test_patch_dict_decorator_resolution(self): + # bpo-35512: Ensure that patch with a string target resolves to + # the new dictionary during function call + original = support.target.copy() + + @patch.dict('unittest.test.testmock.support.target', {'bar': 'BAR'}) + def test(): + self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) + + try: + support.target = {'foo': 'BAZ'} + test() + self.assertEqual(support.target, {'foo': 'BAZ'}) + finally: + support.target = original + + + def test_patch_spec_set(self): + @patch('%s.SomeClass' % __name__, spec=SomeClass, spec_set=True) + def test(MockClass): + MockClass.z = 'foo' + + self.assertRaises(AttributeError, test) + + @patch.object(support, 'SomeClass', spec=SomeClass, spec_set=True) + def test(MockClass): + MockClass.z = 'foo' + + self.assertRaises(AttributeError, test) + @patch('%s.SomeClass' % __name__, spec_set=True) + def test(MockClass): + MockClass.z = 'foo' + + self.assertRaises(AttributeError, test) + + @patch.object(support, 'SomeClass', spec_set=True) + def test(MockClass): + MockClass.z = 'foo' + + self.assertRaises(AttributeError, test) + + + def test_spec_set_inherit(self): + @patch('%s.SomeClass' % __name__, spec_set=True) + def test(MockClass): + instance = MockClass() + instance.z = 'foo' + + self.assertRaises(AttributeError, test) + + + def test_patch_start_stop(self): + original = something + patcher = patch('%s.something' % __name__) + self.assertIs(something, original) + mock = patcher.start() + try: + self.assertIsNot(mock, original) + self.assertIs(something, mock) + finally: + patcher.stop() + self.assertIs(something, original) + + + def test_stop_without_start(self): + # bpo-36366: calling stop without start will return None. + patcher = patch(foo_name, 'bar', 3) + self.assertIsNone(patcher.stop()) + + + def test_stop_idempotent(self): + # bpo-36366: calling stop on an already stopped patch will return None. + patcher = patch(foo_name, 'bar', 3) + + patcher.start() + patcher.stop() + self.assertIsNone(patcher.stop()) + + + def test_patchobject_start_stop(self): + original = something + patcher = patch.object(PTModule, 'something', 'foo') + self.assertIs(something, original) + replaced = patcher.start() + try: + self.assertEqual(replaced, 'foo') + self.assertIs(something, replaced) + finally: + patcher.stop() + self.assertIs(something, original) + + + def test_patch_dict_start_stop(self): + d = {'foo': 'bar'} + original = d.copy() + patcher = patch.dict(d, [('spam', 'eggs')], clear=True) + self.assertEqual(d, original) + + patcher.start() + try: + self.assertEqual(d, {'spam': 'eggs'}) + finally: + patcher.stop() + self.assertEqual(d, original) + + + def test_patch_dict_stop_without_start(self): + d = {'foo': 'bar'} + original = d.copy() + patcher = patch.dict(d, [('spam', 'eggs')], clear=True) + self.assertFalse(patcher.stop()) + self.assertEqual(d, original) + + + def test_patch_dict_class_decorator(self): + this = self + d = {'spam': 'eggs'} + original = d.copy() + + class Test(object): + def test_first(self): + this.assertEqual(d, {'foo': 'bar'}) + def test_second(self): + this.assertEqual(d, {'foo': 'bar'}) + + Test = patch.dict(d, {'foo': 'bar'}, clear=True)(Test) + self.assertEqual(d, original) + + test = Test() + + test.test_first() + self.assertEqual(d, original) + + test.test_second() + self.assertEqual(d, original) + + test = Test() + + test.test_first() + self.assertEqual(d, original) + + test.test_second() + self.assertEqual(d, original) + + + def test_get_only_proxy(self): + class Something(object): + foo = 'foo' + class SomethingElse: + foo = 'foo' + + for thing in Something, SomethingElse, Something(), SomethingElse: + proxy = _get_proxy(thing) + + @patch.object(proxy, 'foo', 'bar') + def test(): + self.assertEqual(proxy.foo, 'bar') + test() + self.assertEqual(proxy.foo, 'foo') + self.assertEqual(thing.foo, 'foo') + self.assertNotIn('foo', proxy.__dict__) + + + def test_get_set_delete_proxy(self): + class Something(object): + foo = 'foo' + class SomethingElse: + foo = 'foo' + + for thing in Something, SomethingElse, Something(), SomethingElse: + proxy = _get_proxy(Something, get_only=False) + + @patch.object(proxy, 'foo', 'bar') + def test(): + self.assertEqual(proxy.foo, 'bar') + test() + self.assertEqual(proxy.foo, 'foo') + self.assertEqual(thing.foo, 'foo') + self.assertNotIn('foo', proxy.__dict__) + + + def test_patch_keyword_args(self): + kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, + 'foo': MagicMock()} + + patcher = patch(foo_name, **kwargs) + mock = patcher.start() + patcher.stop() + + self.assertRaises(KeyError, mock) + self.assertEqual(mock.foo.bar(), 33) + self.assertIsInstance(mock.foo, MagicMock) + + + def test_patch_object_keyword_args(self): + kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, + 'foo': MagicMock()} + + patcher = patch.object(Foo, 'f', **kwargs) + mock = patcher.start() + patcher.stop() + + self.assertRaises(KeyError, mock) + self.assertEqual(mock.foo.bar(), 33) + self.assertIsInstance(mock.foo, MagicMock) + + + def test_patch_dict_keyword_args(self): + original = {'foo': 'bar'} + copy = original.copy() + + patcher = patch.dict(original, foo=3, bar=4, baz=5) + patcher.start() + + try: + self.assertEqual(original, dict(foo=3, bar=4, baz=5)) + finally: + patcher.stop() + + self.assertEqual(original, copy) + + + def test_autospec(self): + class Boo(object): + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass + foo = 'bar' + + class Bar(object): + def a(self): pass + + def _test(mock): + mock(1) + mock.assert_called_with(1) + self.assertRaises(TypeError, mock) + + def _test2(mock): + mock.f(1) + mock.f.assert_called_with(1) + self.assertRaises(TypeError, mock.f) + + mock.g() + mock.g.assert_called_with() + self.assertRaises(TypeError, mock.g, 1) + + self.assertRaises(AttributeError, getattr, mock, 'h') + + mock.foo.lower() + mock.foo.lower.assert_called_with() + self.assertRaises(AttributeError, getattr, mock.foo, 'bar') + + mock.Bar() + mock.Bar.assert_called_with() + + mock.Bar.a() + mock.Bar.a.assert_called_with() + self.assertRaises(TypeError, mock.Bar.a, 1) + + mock.Bar().a() + mock.Bar().a.assert_called_with() + self.assertRaises(TypeError, mock.Bar().a, 1) + + self.assertRaises(AttributeError, getattr, mock.Bar, 'b') + self.assertRaises(AttributeError, getattr, mock.Bar(), 'b') + + def function(mock): + _test(mock) + _test2(mock) + _test2(mock(1)) + self.assertIs(mock, Foo) + return mock + + test = patch(foo_name, autospec=True)(function) + + mock = test() + self.assertIsNot(Foo, mock) + # test patching a second time works + test() + + module = sys.modules[__name__] + test = patch.object(module, 'Foo', autospec=True)(function) + + mock = test() + self.assertIsNot(Foo, mock) + # test patching a second time works + test() + + + def test_autospec_function(self): + @patch('%s.function' % __name__, autospec=True) + def test(mock): + function.assert_not_called() + self.assertRaises(AssertionError, function.assert_called) + self.assertRaises(AssertionError, function.assert_called_once) + function(1) + self.assertRaises(AssertionError, function.assert_not_called) + function.assert_called_with(1) + function.assert_called() + function.assert_called_once() + function(2, 3) + function.assert_called_with(2, 3) + + self.assertRaises(TypeError, function) + self.assertRaises(AttributeError, getattr, function, 'foo') + + test() + + + def test_autospec_keywords(self): + @patch('%s.function' % __name__, autospec=True, + return_value=3) + def test(mock_function): + #self.assertEqual(function.abc, 'foo') + return function(1, 2) + + result = test() + self.assertEqual(result, 3) + + + def test_autospec_staticmethod(self): + with patch('%s.Foo.static_method' % __name__, autospec=True) as method: + Foo.static_method() + method.assert_called_once_with() + + + def test_autospec_classmethod(self): + with patch('%s.Foo.class_method' % __name__, autospec=True) as method: + Foo.class_method() + method.assert_called_once_with() + + + def test_autospec_with_new(self): + patcher = patch('%s.function' % __name__, new=3, autospec=True) + self.assertRaises(TypeError, patcher.start) + + module = sys.modules[__name__] + patcher = patch.object(module, 'function', new=3, autospec=True) + self.assertRaises(TypeError, patcher.start) + + + def test_autospec_with_object(self): + class Bar(Foo): + extra = [] + + patcher = patch(foo_name, autospec=Bar) + mock = patcher.start() + try: + self.assertIsInstance(mock, Bar) + self.assertIsInstance(mock.extra, list) + finally: + patcher.stop() + + + def test_autospec_inherits(self): + FooClass = Foo + patcher = patch(foo_name, autospec=True) + mock = patcher.start() + try: + self.assertIsInstance(mock, FooClass) + self.assertIsInstance(mock(3), FooClass) + finally: + patcher.stop() + + + def test_autospec_name(self): + patcher = patch(foo_name, autospec=True) + mock = patcher.start() + + try: + self.assertIn(" name='Foo'", repr(mock)) + self.assertIn(" name='Foo.f'", repr(mock.f)) + self.assertIn(" name='Foo()'", repr(mock(None))) + self.assertIn(" name='Foo().f'", repr(mock(None).f)) + finally: + patcher.stop() + + + def test_tracebacks(self): + @patch.object(Foo, 'f', object()) + def test(): + raise AssertionError + try: + test() + except: + err = sys.exc_info() + + result = unittest.TextTestResult(None, None, 0) + traceback = result._exc_info_to_string(err, self) + self.assertIn('raise AssertionError', traceback) + + + def test_new_callable_patch(self): + patcher = patch(foo_name, new_callable=NonCallableMagicMock) + + m1 = patcher.start() + patcher.stop() + m2 = patcher.start() + patcher.stop() + + self.assertIsNot(m1, m2) + for mock in m1, m2: + self.assertNotCallable(m1) + + + def test_new_callable_patch_object(self): + patcher = patch.object(Foo, 'f', new_callable=NonCallableMagicMock) + + m1 = patcher.start() + patcher.stop() + m2 = patcher.start() + patcher.stop() + + self.assertIsNot(m1, m2) + for mock in m1, m2: + self.assertNotCallable(m1) + + + def test_new_callable_keyword_arguments(self): + class Bar(object): + kwargs = None + def __init__(self, **kwargs): + Bar.kwargs = kwargs + + patcher = patch(foo_name, new_callable=Bar, arg1=1, arg2=2) + m = patcher.start() + try: + self.assertIs(type(m), Bar) + self.assertEqual(Bar.kwargs, dict(arg1=1, arg2=2)) + finally: + patcher.stop() + + + def test_new_callable_spec(self): + class Bar(object): + kwargs = None + def __init__(self, **kwargs): + Bar.kwargs = kwargs + + patcher = patch(foo_name, new_callable=Bar, spec=Bar) + patcher.start() + try: + self.assertEqual(Bar.kwargs, dict(spec=Bar)) + finally: + patcher.stop() + + patcher = patch(foo_name, new_callable=Bar, spec_set=Bar) + patcher.start() + try: + self.assertEqual(Bar.kwargs, dict(spec_set=Bar)) + finally: + patcher.stop() + + + def test_new_callable_create(self): + non_existent_attr = '%s.weeeee' % foo_name + p = patch(non_existent_attr, new_callable=NonCallableMock) + self.assertRaises(AttributeError, p.start) + + p = patch(non_existent_attr, new_callable=NonCallableMock, + create=True) + m = p.start() + try: + self.assertNotCallable(m, magic=False) + finally: + p.stop() + + + def test_new_callable_incompatible_with_new(self): + self.assertRaises( + ValueError, patch, foo_name, new=object(), new_callable=MagicMock + ) + self.assertRaises( + ValueError, patch.object, Foo, 'f', new=object(), + new_callable=MagicMock + ) + + + def test_new_callable_incompatible_with_autospec(self): + self.assertRaises( + ValueError, patch, foo_name, new_callable=MagicMock, + autospec=True + ) + self.assertRaises( + ValueError, patch.object, Foo, 'f', new_callable=MagicMock, + autospec=True + ) + + + def test_new_callable_inherit_for_mocks(self): + class MockSub(Mock): + pass + + MockClasses = ( + NonCallableMock, NonCallableMagicMock, MagicMock, Mock, MockSub + ) + for Klass in MockClasses: + for arg in 'spec', 'spec_set': + kwargs = {arg: True} + p = patch(foo_name, new_callable=Klass, **kwargs) + m = p.start() + try: + instance = m.return_value + self.assertRaises(AttributeError, getattr, instance, 'x') + finally: + p.stop() + + + def test_new_callable_inherit_non_mock(self): + class NotAMock(object): + def __init__(self, spec): + self.spec = spec + + p = patch(foo_name, new_callable=NotAMock, spec=True) + m = p.start() + try: + self.assertTrue(is_instance(m, NotAMock)) + self.assertRaises(AttributeError, getattr, m, 'return_value') + finally: + p.stop() + + self.assertEqual(m.spec, Foo) + + + def test_new_callable_class_decorating(self): + test = self + original = Foo + class SomeTest(object): + + def _test(self, mock_foo): + test.assertIsNot(Foo, original) + test.assertIs(Foo, mock_foo) + test.assertIsInstance(Foo, SomeClass) + + def test_two(self, mock_foo): + self._test(mock_foo) + def test_one(self, mock_foo): + self._test(mock_foo) + + SomeTest = patch(foo_name, new_callable=SomeClass)(SomeTest) + SomeTest().test_one() + SomeTest().test_two() + self.assertIs(Foo, original) + + + def test_patch_multiple(self): + original_foo = Foo + original_f = Foo.f + original_g = Foo.g + + patcher1 = patch.multiple(foo_name, f=1, g=2) + patcher2 = patch.multiple(Foo, f=1, g=2) + + for patcher in patcher1, patcher2: + patcher.start() + try: + self.assertIs(Foo, original_foo) + self.assertEqual(Foo.f, 1) + self.assertEqual(Foo.g, 2) + finally: + patcher.stop() + + self.assertIs(Foo, original_foo) + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + @patch.multiple(foo_name, f=3, g=4) + def test(): + self.assertIs(Foo, original_foo) + self.assertEqual(Foo.f, 3) + self.assertEqual(Foo.g, 4) + + test() + + + def test_patch_multiple_no_kwargs(self): + self.assertRaises(ValueError, patch.multiple, foo_name) + self.assertRaises(ValueError, patch.multiple, Foo) + + + def test_patch_multiple_create_mocks(self): + original_foo = Foo + original_f = Foo.f + original_g = Foo.g + + @patch.multiple(foo_name, f=DEFAULT, g=3, foo=DEFAULT) + def test(f, foo): + self.assertIs(Foo, original_foo) + self.assertIs(Foo.f, f) + self.assertEqual(Foo.g, 3) + self.assertIs(Foo.foo, foo) + self.assertTrue(is_instance(f, MagicMock)) + self.assertTrue(is_instance(foo, MagicMock)) + + test() + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_create_mocks_different_order(self): + original_f = Foo.f + original_g = Foo.g + + patcher = patch.object(Foo, 'f', 3) + patcher.attribute_name = 'f' + + other = patch.object(Foo, 'g', DEFAULT) + other.attribute_name = 'g' + patcher.additional_patchers = [other] + + @patcher + def test(g): + self.assertIs(Foo.g, g) + self.assertEqual(Foo.f, 3) + + test() + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_stacked_decorators(self): + original_foo = Foo + original_f = Foo.f + original_g = Foo.g + + @patch.multiple(foo_name, f=DEFAULT) + @patch.multiple(foo_name, foo=DEFAULT) + @patch(foo_name + '.g') + def test1(g, **kwargs): + _test(g, **kwargs) + + @patch.multiple(foo_name, f=DEFAULT) + @patch(foo_name + '.g') + @patch.multiple(foo_name, foo=DEFAULT) + def test2(g, **kwargs): + _test(g, **kwargs) + + @patch(foo_name + '.g') + @patch.multiple(foo_name, f=DEFAULT) + @patch.multiple(foo_name, foo=DEFAULT) + def test3(g, **kwargs): + _test(g, **kwargs) + + def _test(g, **kwargs): + f = kwargs.pop('f') + foo = kwargs.pop('foo') + self.assertFalse(kwargs) + + self.assertIs(Foo, original_foo) + self.assertIs(Foo.f, f) + self.assertIs(Foo.g, g) + self.assertIs(Foo.foo, foo) + self.assertTrue(is_instance(f, MagicMock)) + self.assertTrue(is_instance(g, MagicMock)) + self.assertTrue(is_instance(foo, MagicMock)) + + test1() + test2() + test3() + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_create_mocks_patcher(self): + original_foo = Foo + original_f = Foo.f + original_g = Foo.g + + patcher = patch.multiple(foo_name, f=DEFAULT, g=3, foo=DEFAULT) + + result = patcher.start() + try: + f = result['f'] + foo = result['foo'] + self.assertEqual(set(result), set(['f', 'foo'])) + + self.assertIs(Foo, original_foo) + self.assertIs(Foo.f, f) + self.assertIs(Foo.foo, foo) + self.assertTrue(is_instance(f, MagicMock)) + self.assertTrue(is_instance(foo, MagicMock)) + finally: + patcher.stop() + + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_decorating_class(self): + test = self + original_foo = Foo + original_f = Foo.f + original_g = Foo.g + + class SomeTest(object): + + def _test(self, f, foo): + test.assertIs(Foo, original_foo) + test.assertIs(Foo.f, f) + test.assertEqual(Foo.g, 3) + test.assertIs(Foo.foo, foo) + test.assertTrue(is_instance(f, MagicMock)) + test.assertTrue(is_instance(foo, MagicMock)) + + def test_two(self, f, foo): + self._test(f, foo) + def test_one(self, f, foo): + self._test(f, foo) + + SomeTest = patch.multiple( + foo_name, f=DEFAULT, g=3, foo=DEFAULT + )(SomeTest) + + thing = SomeTest() + thing.test_one() + thing.test_two() + + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_create(self): + patcher = patch.multiple(Foo, blam='blam') + self.assertRaises(AttributeError, patcher.start) + + patcher = patch.multiple(Foo, blam='blam', create=True) + patcher.start() + try: + self.assertEqual(Foo.blam, 'blam') + finally: + patcher.stop() + + self.assertFalse(hasattr(Foo, 'blam')) + + + def test_patch_multiple_spec_set(self): + # if spec_set works then we can assume that spec and autospec also + # work as the underlying machinery is the same + patcher = patch.multiple(Foo, foo=DEFAULT, spec_set=['a', 'b']) + result = patcher.start() + try: + self.assertEqual(Foo.foo, result['foo']) + Foo.foo.a(1) + Foo.foo.b(2) + Foo.foo.a.assert_called_with(1) + Foo.foo.b.assert_called_with(2) + self.assertRaises(AttributeError, setattr, Foo.foo, 'c', None) + finally: + patcher.stop() + + + def test_patch_multiple_new_callable(self): + class Thing(object): + pass + + patcher = patch.multiple( + Foo, f=DEFAULT, g=DEFAULT, new_callable=Thing + ) + result = patcher.start() + try: + self.assertIs(Foo.f, result['f']) + self.assertIs(Foo.g, result['g']) + self.assertIsInstance(Foo.f, Thing) + self.assertIsInstance(Foo.g, Thing) + self.assertIsNot(Foo.f, Foo.g) + finally: + patcher.stop() + + + def test_nested_patch_failure(self): + original_f = Foo.f + original_g = Foo.g + + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'missing', 1) + @patch.object(Foo, 'f', 1) + def thing1(): pass + + @patch.object(Foo, 'missing', 1) + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'f', 1) + def thing2(): pass + + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'f', 1) + @patch.object(Foo, 'missing', 1) + def thing3(): pass + + for func in thing1, thing2, thing3: + self.assertRaises(AttributeError, func) + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_new_callable_failure(self): + original_f = Foo.f + original_g = Foo.g + original_foo = Foo.foo + + def crasher(): + raise NameError('crasher') + + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'foo', new_callable=crasher) + @patch.object(Foo, 'f', 1) + def thing1(): pass + + @patch.object(Foo, 'foo', new_callable=crasher) + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'f', 1) + def thing2(): pass + + @patch.object(Foo, 'g', 1) + @patch.object(Foo, 'f', 1) + @patch.object(Foo, 'foo', new_callable=crasher) + def thing3(): pass + + for func in thing1, thing2, thing3: + self.assertRaises(NameError, func) + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + self.assertEqual(Foo.foo, original_foo) + + + def test_patch_multiple_failure(self): + original_f = Foo.f + original_g = Foo.g + + patcher = patch.object(Foo, 'f', 1) + patcher.attribute_name = 'f' + + good = patch.object(Foo, 'g', 1) + good.attribute_name = 'g' + + bad = patch.object(Foo, 'missing', 1) + bad.attribute_name = 'missing' + + for additionals in [good, bad], [bad, good]: + patcher.additional_patchers = additionals + + @patcher + def func(): pass + + self.assertRaises(AttributeError, func) + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + + + def test_patch_multiple_new_callable_failure(self): + original_f = Foo.f + original_g = Foo.g + original_foo = Foo.foo + + def crasher(): + raise NameError('crasher') + + patcher = patch.object(Foo, 'f', 1) + patcher.attribute_name = 'f' + + good = patch.object(Foo, 'g', 1) + good.attribute_name = 'g' + + bad = patch.object(Foo, 'foo', new_callable=crasher) + bad.attribute_name = 'foo' + + for additionals in [good, bad], [bad, good]: + patcher.additional_patchers = additionals + + @patcher + def func(): pass + + self.assertRaises(NameError, func) + self.assertEqual(Foo.f, original_f) + self.assertEqual(Foo.g, original_g) + self.assertEqual(Foo.foo, original_foo) + + + def test_patch_multiple_string_subclasses(self): + Foo = type('Foo', (str,), {'fish': 'tasty'}) + foo = Foo() + @patch.multiple(foo, fish='nearly gone') + def test(): + self.assertEqual(foo.fish, 'nearly gone') + + test() + self.assertEqual(foo.fish, 'tasty') + + + @patch('unittest.mock.patch.TEST_PREFIX', 'foo') + def test_patch_test_prefix(self): + class Foo(object): + thing = 'original' + + def foo_one(self): + return self.thing + def foo_two(self): + return self.thing + def test_one(self): + return self.thing + def test_two(self): + return self.thing + + Foo = patch.object(Foo, 'thing', 'changed')(Foo) + + foo = Foo() + self.assertEqual(foo.foo_one(), 'changed') + self.assertEqual(foo.foo_two(), 'changed') + self.assertEqual(foo.test_one(), 'original') + self.assertEqual(foo.test_two(), 'original') + + + @patch('unittest.mock.patch.TEST_PREFIX', 'bar') + def test_patch_dict_test_prefix(self): + class Foo(object): + def bar_one(self): + return dict(the_dict) + def bar_two(self): + return dict(the_dict) + def test_one(self): + return dict(the_dict) + def test_two(self): + return dict(the_dict) + + the_dict = {'key': 'original'} + Foo = patch.dict(the_dict, key='changed')(Foo) + + foo =Foo() + self.assertEqual(foo.bar_one(), {'key': 'changed'}) + self.assertEqual(foo.bar_two(), {'key': 'changed'}) + self.assertEqual(foo.test_one(), {'key': 'original'}) + self.assertEqual(foo.test_two(), {'key': 'original'}) + + + def test_patch_with_spec_mock_repr(self): + for arg in ('spec', 'autospec', 'spec_set'): + p = patch('%s.SomeClass' % __name__, **{arg: True}) + m = p.start() + try: + self.assertIn(" name='SomeClass'", repr(m)) + self.assertIn(" name='SomeClass.class_attribute'", + repr(m.class_attribute)) + self.assertIn(" name='SomeClass()'", repr(m())) + self.assertIn(" name='SomeClass().class_attribute'", + repr(m().class_attribute)) + finally: + p.stop() + + + def test_patch_nested_autospec_repr(self): + with patch('unittest.test.testmock.support', autospec=True) as m: + self.assertIn(" name='support.SomeClass.wibble()'", + repr(m.SomeClass.wibble())) + self.assertIn(" name='support.SomeClass().wibble()'", + repr(m.SomeClass().wibble())) + + + + def test_mock_calls_with_patch(self): + for arg in ('spec', 'autospec', 'spec_set'): + p = patch('%s.SomeClass' % __name__, **{arg: True}) + m = p.start() + try: + m.wibble() + + kalls = [call.wibble()] + self.assertEqual(m.mock_calls, kalls) + self.assertEqual(m.method_calls, kalls) + self.assertEqual(m.wibble.mock_calls, [call()]) + + result = m() + kalls.append(call()) + self.assertEqual(m.mock_calls, kalls) + + result.wibble() + kalls.append(call().wibble()) + self.assertEqual(m.mock_calls, kalls) + + self.assertEqual(result.mock_calls, [call.wibble()]) + self.assertEqual(result.wibble.mock_calls, [call()]) + self.assertEqual(result.method_calls, [call.wibble()]) + finally: + p.stop() + + + def test_patch_imports_lazily(self): + p1 = patch('squizz.squozz') + self.assertRaises(ImportError, p1.start) + + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + + squizz.squozz = 6 + p1 = patch('squizz.squozz') + squizz.squozz = 3 + p1.start() + p1.stop() + self.assertEqual(squizz.squozz, 3) + + def test_patch_propagates_exc_on_exit(self): + class holder: + exc_info = None, None, None + + class custom_patch(_patch): + def __exit__(self, etype=None, val=None, tb=None): + _patch.__exit__(self, etype, val, tb) + holder.exc_info = etype, val, tb + stop = __exit__ + + def with_custom_patch(target): + getter, attribute = _get_target(target) + return custom_patch( + getter, attribute, DEFAULT, None, False, None, + None, None, {} + ) + + @with_custom_patch('squizz.squozz') + def test(mock): + raise RuntimeError + + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + + self.assertRaises(RuntimeError, test) + + self.assertIs(holder.exc_info[0], RuntimeError) + self.assertIsNotNone(holder.exc_info[1], + 'exception value not propagated') + self.assertIsNotNone(holder.exc_info[2], + 'exception traceback not propagated') + + + def test_create_and_specs(self): + for kwarg in ('spec', 'spec_set', 'autospec'): + p = patch('%s.doesnotexist' % __name__, create=True, + **{kwarg: True}) + self.assertRaises(TypeError, p.start) + self.assertRaises(NameError, lambda: doesnotexist) + + # check that spec with create is innocuous if the original exists + p = patch(MODNAME, create=True, **{kwarg: True}) + p.start() + p.stop() + + + def test_multiple_specs(self): + original = PTModule + for kwarg in ('spec', 'spec_set'): + p = patch(MODNAME, autospec=0, **{kwarg: 0}) + self.assertRaises(TypeError, p.start) + self.assertIs(PTModule, original) + + for kwarg in ('spec', 'autospec'): + p = patch(MODNAME, spec_set=0, **{kwarg: 0}) + self.assertRaises(TypeError, p.start) + self.assertIs(PTModule, original) + + for kwarg in ('spec_set', 'autospec'): + p = patch(MODNAME, spec=0, **{kwarg: 0}) + self.assertRaises(TypeError, p.start) + self.assertIs(PTModule, original) + + + def test_specs_false_instead_of_none(self): + p = patch(MODNAME, spec=False, spec_set=False, autospec=False) + mock = p.start() + try: + # no spec should have been set, so attribute access should not fail + mock.does_not_exist + mock.does_not_exist = 3 + finally: + p.stop() + + + def test_falsey_spec(self): + for kwarg in ('spec', 'autospec', 'spec_set'): + p = patch(MODNAME, **{kwarg: 0}) + m = p.start() + try: + self.assertRaises(AttributeError, getattr, m, 'doesnotexit') + finally: + p.stop() + + + def test_spec_set_true(self): + for kwarg in ('spec', 'autospec'): + p = patch(MODNAME, spec_set=True, **{kwarg: True}) + m = p.start() + try: + self.assertRaises(AttributeError, setattr, m, + 'doesnotexist', 'something') + self.assertRaises(AttributeError, getattr, m, 'doesnotexist') + finally: + p.stop() + + + def test_callable_spec_as_list(self): + spec = ('__call__',) + p = patch(MODNAME, spec=spec) + m = p.start() + try: + self.assertTrue(callable(m)) + finally: + p.stop() + + + def test_not_callable_spec_as_list(self): + spec = ('foo', 'bar') + p = patch(MODNAME, spec=spec) + m = p.start() + try: + self.assertFalse(callable(m)) + finally: + p.stop() + + + def test_patch_stopall(self): + unlink = os.unlink + chdir = os.chdir + path = os.path + patch('os.unlink', something).start() + patch('os.chdir', something_else).start() + + @patch('os.path') + def patched(mock_path): + patch.stopall() + self.assertIs(os.path, mock_path) + self.assertIs(os.unlink, unlink) + self.assertIs(os.chdir, chdir) + + patched() + self.assertIs(os.path, path) + + def test_stopall_lifo(self): + stopped = [] + class thing(object): + one = two = three = None + + def get_patch(attribute): + class mypatch(_patch): + def stop(self): + stopped.append(attribute) + return super(mypatch, self).stop() + return mypatch(lambda: thing, attribute, None, None, + False, None, None, None, {}) + [get_patch(val).start() for val in ("one", "two", "three")] + patch.stopall() + + self.assertEqual(stopped, ["three", "two", "one"]) + + def test_patch_dict_stopall(self): + dic1 = {} + dic2 = {1: 'a'} + dic3 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + origdic3 = dic3.copy() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2, {2: 'b'}).start() + + @patch.dict(dic3) + def patched(): + del dic3[1] + + patched() + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + patch.stopall() + + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + + def test_patch_and_patch_dict_stopall(self): + original_unlink = os.unlink + original_chdir = os.chdir + dic1 = {} + dic2 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + + patch('os.unlink', something).start() + patch('os.chdir', something_else).start() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2).start() + del dic2[1] + + self.assertIsNot(os.unlink, original_unlink) + self.assertIsNot(os.chdir, original_chdir) + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + patch.stopall() + self.assertIs(os.unlink, original_unlink) + self.assertIs(os.chdir, original_chdir) + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + + + def test_special_attrs(self): + def foo(x=0): + """TEST""" + return x + with patch.object(foo, '__defaults__', (1, )): + self.assertEqual(foo(), 1) + self.assertEqual(foo(), 0) + + with patch.object(foo, '__doc__', "FUN"): + self.assertEqual(foo.__doc__, "FUN") + self.assertEqual(foo.__doc__, "TEST") + + with patch.object(foo, '__module__', "testpatch2"): + self.assertEqual(foo.__module__, "testpatch2") + self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch') + + with patch.object(foo, '__annotations__', dict([('s', 1, )])): + self.assertEqual(foo.__annotations__, dict([('s', 1, )])) + self.assertEqual(foo.__annotations__, dict()) + + def foo(*a, x=0): + return x + with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): + self.assertEqual(foo(), 1) + self.assertEqual(foo(), 0) + + def test_patch_orderdict(self): + foo = OrderedDict() + foo['a'] = object() + foo['b'] = 'python' + + original = foo.copy() + update_values = list(zip('cdefghijklmnopqrstuvwxyz', range(26))) + patched_values = list(foo.items()) + update_values + + with patch.dict(foo, OrderedDict(update_values)): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + + with patch.dict(foo, update_values): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + + def test_dotted_but_module_not_loaded(self): + # This exercises the AttributeError branch of _dot_lookup. + + # make sure it's there + import unittest.test.testmock.support + # now make sure it's not: + with patch.dict('sys.modules'): + del sys.modules['unittest.test.testmock.support'] + del sys.modules['unittest.test.testmock'] + del sys.modules['unittest.test'] + del sys.modules['unittest'] + + # now make sure we can patch based on a dotted path: + @patch('unittest.test.testmock.support.X') + def test(mock): + pass + test() + + + def test_invalid_target(self): + with self.assertRaises(TypeError): + patch('') + + + def test_cant_set_kwargs_when_passing_a_mock(self): + @patch('unittest.test.testmock.support.X', new=object(), x=1) + def test(): pass + with self.assertRaises(TypeError): + test() + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py b/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py new file mode 100644 index 0000000000..59f52338d4 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py @@ -0,0 +1,176 @@ +import unittest +from unittest import mock + + +class SampleObject: + + def method_sample1(self): pass + + def method_sample2(self): pass + + +class TestSealable(unittest.TestCase): + + def test_attributes_return_more_mocks_by_default(self): + m = mock.Mock() + + self.assertIsInstance(m.test, mock.Mock) + self.assertIsInstance(m.test(), mock.Mock) + self.assertIsInstance(m.test().test2(), mock.Mock) + + def test_new_attributes_cannot_be_accessed_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test + with self.assertRaises(AttributeError): + m() + + def test_new_attributes_cannot_be_set_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test = 1 + + def test_existing_attributes_can_be_set_on_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + m.test.test2 = 2 + self.assertEqual(m.test.test2, 2) + + def test_new_attributes_cannot_be_set_on_child_of_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test.test3 = 1 + + def test_existing_attributes_allowed_after_seal(self): + m = mock.Mock() + + m.test.return_value = 3 + + mock.seal(m) + self.assertEqual(m.test(), 3) + + def test_initialized_attributes_allowed_after_seal(self): + m = mock.Mock(test_value=1) + + mock.seal(m) + self.assertEqual(m.test_value, 1) + + def test_call_on_sealed_mock_fails(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m() + + def test_call_on_defined_sealed_mock_succeeds(self): + m = mock.Mock(return_value=5) + + mock.seal(m) + self.assertEqual(m(), 5) + + def test_seals_recurse_on_added_attributes(self): + m = mock.Mock() + + m.test1.test2().test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2().test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2().test4 + with self.assertRaises(AttributeError): + m.test1.test3 + + def test_seals_recurse_on_magic_methods(self): + m = mock.MagicMock() + + m.test1.test2["a"].test3 = 4 + m.test1.test3[2:5].test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2["a"].test3, 4) + self.assertEqual(m.test1.test2[2:5].test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2["a"].test4 + with self.assertRaises(AttributeError): + m.test1.test3[2:5].test4 + + def test_seals_dont_recurse_on_manual_attributes(self): + m = mock.Mock(name="root_mock") + + m.test1.test2 = mock.Mock(name="not_sealed") + m.test1.test2.test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2.test3, 4) + m.test1.test2.test4 # Does not raise + m.test1.test2.test4 = 1 # Does not raise + + def test_integration_with_spec_att_definition(self): + """You are not restricted when using mock with spec""" + m = mock.Mock(SampleObject) + + m.attr_sample1 = 1 + m.attr_sample3 = 3 + + mock.seal(m) + self.assertEqual(m.attr_sample1, 1) + self.assertEqual(m.attr_sample3, 3) + with self.assertRaises(AttributeError): + m.attr_sample2 + + def test_integration_with_spec_method_definition(self): + """You need to defin the methods, even if they are in the spec""" + m = mock.Mock(SampleObject) + + m.method_sample1.return_value = 1 + + mock.seal(m) + self.assertEqual(m.method_sample1(), 1) + with self.assertRaises(AttributeError): + m.method_sample2() + + def test_integration_with_spec_method_definition_respects_spec(self): + """You cannot define methods out of the spec""" + m = mock.Mock(SampleObject) + + with self.assertRaises(AttributeError): + m.method_sample3.return_value = 3 + + def test_sealed_exception_has_attribute_name(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.SECRETE_name + self.assertIn("SECRETE_name", str(cm.exception)) + + def test_attribute_chain_is_maintained(self): + m = mock.Mock(name="mock_name") + m.test1.test2.test3.test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1.test2.test3.test4.boom + self.assertIn("mock_name.test1.test2.test3.test4.boom", str(cm.exception)) + + def test_call_chain_is_maintained(self): + m = mock.Mock() + m.test1().test2.test3().test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1().test2.test3().test4() + self.assertIn("mock.test1().test2.test3().test4", str(cm.exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py b/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py new file mode 100644 index 0000000000..de53509803 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py @@ -0,0 +1,41 @@ +import unittest +import copy +import pickle +from unittest.mock import sentinel, DEFAULT + + +class SentinelTest(unittest.TestCase): + + def testSentinels(self): + self.assertEqual(sentinel.whatever, sentinel.whatever, + 'sentinel not stored') + self.assertNotEqual(sentinel.whatever, sentinel.whateverelse, + 'sentinel should be unique') + + + def testSentinelName(self): + self.assertEqual(str(sentinel.whatever), 'sentinel.whatever', + 'sentinel name incorrect') + + + def testDEFAULT(self): + self.assertIs(DEFAULT, sentinel.DEFAULT) + + def testBases(self): + # If this doesn't raise an AttributeError then help(mock) is broken + self.assertRaises(AttributeError, lambda: sentinel.__bases__) + + def testPickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL+1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(sentinel.whatever, proto) + unpickled = pickle.loads(pickled) + self.assertIs(unpickled, sentinel.whatever) + + def testCopy(self): + self.assertIs(copy.copy(sentinel.whatever), sentinel.whatever) + self.assertIs(copy.deepcopy(sentinel.whatever), sentinel.whatever) + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py b/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py new file mode 100644 index 0000000000..42ebf3898c --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py @@ -0,0 +1,347 @@ +import unittest +from warnings import catch_warnings + +from unittest.test.testmock.support import is_instance +from unittest.mock import MagicMock, Mock, patch, sentinel, mock_open, call + + + +something = sentinel.Something +something_else = sentinel.SomethingElse + + +class SampleException(Exception): pass + + +class WithTest(unittest.TestCase): + + def test_with_statement(self): + with patch('%s.something' % __name__, sentinel.Something2): + self.assertEqual(something, sentinel.Something2, "unpatched") + self.assertEqual(something, sentinel.Something) + + + def test_with_statement_exception(self): + with self.assertRaises(SampleException): + with patch('%s.something' % __name__, sentinel.Something2): + self.assertEqual(something, sentinel.Something2, "unpatched") + raise SampleException() + self.assertEqual(something, sentinel.Something) + + + def test_with_statement_as(self): + with patch('%s.something' % __name__) as mock_something: + self.assertEqual(something, mock_something, "unpatched") + self.assertTrue(is_instance(mock_something, MagicMock), + "patching wrong type") + self.assertEqual(something, sentinel.Something) + + + def test_patch_object_with_statement(self): + class Foo(object): + something = 'foo' + original = Foo.something + with patch.object(Foo, 'something'): + self.assertNotEqual(Foo.something, original, "unpatched") + self.assertEqual(Foo.something, original) + + + def test_with_statement_nested(self): + with catch_warnings(record=True): + with patch('%s.something' % __name__) as mock_something, patch('%s.something_else' % __name__) as mock_something_else: + self.assertEqual(something, mock_something, "unpatched") + self.assertEqual(something_else, mock_something_else, + "unpatched") + + self.assertEqual(something, sentinel.Something) + self.assertEqual(something_else, sentinel.SomethingElse) + + + def test_with_statement_specified(self): + with patch('%s.something' % __name__, sentinel.Patched) as mock_something: + self.assertEqual(something, mock_something, "unpatched") + self.assertEqual(mock_something, sentinel.Patched, "wrong patch") + self.assertEqual(something, sentinel.Something) + + + def testContextManagerMocking(self): + mock = Mock() + mock.__enter__ = Mock() + mock.__exit__ = Mock() + mock.__exit__.return_value = False + + with mock as m: + self.assertEqual(m, mock.__enter__.return_value) + mock.__enter__.assert_called_with() + mock.__exit__.assert_called_with(None, None, None) + + + def test_context_manager_with_magic_mock(self): + mock = MagicMock() + + with self.assertRaises(TypeError): + with mock: + 'foo' + 3 + mock.__enter__.assert_called_with() + self.assertTrue(mock.__exit__.called) + + + def test_with_statement_same_attribute(self): + with patch('%s.something' % __name__, sentinel.Patched) as mock_something: + self.assertEqual(something, mock_something, "unpatched") + + with patch('%s.something' % __name__) as mock_again: + self.assertEqual(something, mock_again, "unpatched") + + self.assertEqual(something, mock_something, + "restored with wrong instance") + + self.assertEqual(something, sentinel.Something, "not restored") + + + def test_with_statement_imbricated(self): + with patch('%s.something' % __name__) as mock_something: + self.assertEqual(something, mock_something, "unpatched") + + with patch('%s.something_else' % __name__) as mock_something_else: + self.assertEqual(something_else, mock_something_else, + "unpatched") + + self.assertEqual(something, sentinel.Something) + self.assertEqual(something_else, sentinel.SomethingElse) + + + def test_dict_context_manager(self): + foo = {} + with patch.dict(foo, {'a': 'b'}): + self.assertEqual(foo, {'a': 'b'}) + self.assertEqual(foo, {}) + + with self.assertRaises(NameError): + with patch.dict(foo, {'a': 'b'}): + self.assertEqual(foo, {'a': 'b'}) + raise NameError('Konrad') + + self.assertEqual(foo, {}) + + def test_double_patch_instance_method(self): + class C: + def f(self): pass + + c = C() + + with patch.object(c, 'f', autospec=True) as patch1: + with patch.object(c, 'f', autospec=True) as patch2: + c.f() + self.assertEqual(patch2.call_count, 1) + self.assertEqual(patch1.call_count, 0) + c.f() + self.assertEqual(patch1.call_count, 1) + + +class TestMockOpen(unittest.TestCase): + + def test_mock_open(self): + mock = mock_open() + with patch('%s.open' % __name__, mock, create=True) as patched: + self.assertIs(patched, mock) + open('foo') + + mock.assert_called_once_with('foo') + + + def test_mock_open_context_manager(self): + mock = mock_open() + handle = mock.return_value + with patch('%s.open' % __name__, mock, create=True): + with open('foo') as f: + f.read() + + expected_calls = [call('foo'), call().__enter__(), call().read(), + call().__exit__(None, None, None)] + self.assertEqual(mock.mock_calls, expected_calls) + self.assertIs(f, handle) + + def test_mock_open_context_manager_multiple_times(self): + mock = mock_open() + with patch('%s.open' % __name__, mock, create=True): + with open('foo') as f: + f.read() + with open('bar') as f: + f.read() + + expected_calls = [ + call('foo'), call().__enter__(), call().read(), + call().__exit__(None, None, None), + call('bar'), call().__enter__(), call().read(), + call().__exit__(None, None, None)] + self.assertEqual(mock.mock_calls, expected_calls) + + def test_explicit_mock(self): + mock = MagicMock() + mock_open(mock) + + with patch('%s.open' % __name__, mock, create=True) as patched: + self.assertIs(patched, mock) + open('foo') + + mock.assert_called_once_with('foo') + + + def test_read_data(self): + mock = mock_open(read_data='foo') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.read() + + self.assertEqual(result, 'foo') + + + def test_readline_data(self): + # Check that readline will return all the lines from the fake file + # And that once fully consumed, readline will return an empty string. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + line2 = h.readline() + line3 = h.readline() + self.assertEqual(line1, 'foo\n') + self.assertEqual(line2, 'bar\n') + self.assertEqual(line3, 'baz\n') + self.assertEqual(h.readline(), '') + + # Check that we properly emulate a file that doesn't end in a newline + mock = mock_open(read_data='foo') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readline() + self.assertEqual(result, 'foo') + self.assertEqual(h.readline(), '') + + + def test_dunder_iter_data(self): + # Check that dunder_iter will return all the lines from the fake file. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + lines = [l for l in h] + self.assertEqual(lines[0], 'foo\n') + self.assertEqual(lines[1], 'bar\n') + self.assertEqual(lines[2], 'baz\n') + self.assertEqual(h.readline(), '') + with self.assertRaises(StopIteration): + next(h) + + def test_next_data(self): + # Check that next will correctly return the next available + # line and plays well with the dunder_iter part. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = next(h) + line2 = next(h) + lines = [l for l in h] + self.assertEqual(line1, 'foo\n') + self.assertEqual(line2, 'bar\n') + self.assertEqual(lines[0], 'baz\n') + self.assertEqual(h.readline(), '') + + def test_readlines_data(self): + # Test that emulating a file that ends in a newline character works + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readlines() + self.assertEqual(result, ['foo\n', 'bar\n', 'baz\n']) + + # Test that files without a final newline will also be correctly + # emulated + mock = mock_open(read_data='foo\nbar\nbaz') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readlines() + + self.assertEqual(result, ['foo\n', 'bar\n', 'baz']) + + + def test_read_bytes(self): + mock = mock_open(read_data=b'\xc6') + with patch('%s.open' % __name__, mock, create=True): + with open('abc', 'rb') as f: + result = f.read() + self.assertEqual(result, b'\xc6') + + + def test_readline_bytes(self): + m = mock_open(read_data=b'abc\ndef\nghi\n') + with patch('%s.open' % __name__, m, create=True): + with open('abc', 'rb') as f: + line1 = f.readline() + line2 = f.readline() + line3 = f.readline() + self.assertEqual(line1, b'abc\n') + self.assertEqual(line2, b'def\n') + self.assertEqual(line3, b'ghi\n') + + + def test_readlines_bytes(self): + m = mock_open(read_data=b'abc\ndef\nghi\n') + with patch('%s.open' % __name__, m, create=True): + with open('abc', 'rb') as f: + result = f.readlines() + self.assertEqual(result, [b'abc\n', b'def\n', b'ghi\n']) + + + def test_mock_open_read_with_argument(self): + # At one point calling read with an argument was broken + # for mocks returned by mock_open + some_data = 'foo\nbar\nbaz' + mock = mock_open(read_data=some_data) + self.assertEqual(mock().read(10), some_data[:10]) + self.assertEqual(mock().read(10), some_data[:10]) + + f = mock() + self.assertEqual(f.read(10), some_data[:10]) + self.assertEqual(f.read(10), some_data[10:]) + + + def test_interleaved_reads(self): + # Test that calling read, readline, and readlines pulls data + # sequentially from the data we preload with + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + rest = h.readlines() + self.assertEqual(line1, 'foo\n') + self.assertEqual(rest, ['bar\n', 'baz\n']) + + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + rest = h.read() + self.assertEqual(line1, 'foo\n') + self.assertEqual(rest, 'bar\nbaz\n') + + + def test_overriding_return_values(self): + mock = mock_open(read_data='foo') + handle = mock() + + handle.read.return_value = 'bar' + handle.readline.return_value = 'bar' + handle.readlines.return_value = ['bar'] + + self.assertEqual(handle.read(), 'bar') + self.assertEqual(handle.readline(), 'bar') + self.assertEqual(handle.readlines(), ['bar']) + + # call repeatedly to check that a StopIteration is not propagated + self.assertEqual(handle.readline(), 'bar') + self.assertEqual(handle.readline(), 'bar') + + +if __name__ == '__main__': + unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/util.py b/Monika After Story/game/python-packages/unittest/util.py new file mode 100644 index 0000000000..050eaed0b3 --- /dev/null +++ b/Monika After Story/game/python-packages/unittest/util.py @@ -0,0 +1,170 @@ +"""Various utility functions.""" + +from collections import namedtuple, Counter +from os.path import commonprefix + +__unittest = True + +_MAX_LENGTH = 80 +_PLACEHOLDER_LEN = 12 +_MIN_BEGIN_LEN = 5 +_MIN_END_LEN = 5 +_MIN_COMMON_LEN = 5 +_MIN_DIFF_LEN = _MAX_LENGTH - \ + (_MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + + _PLACEHOLDER_LEN + _MIN_END_LEN) +assert _MIN_DIFF_LEN >= 0 + +def _shorten(s, prefixlen, suffixlen): + skip = len(s) - prefixlen - suffixlen + if skip > _PLACEHOLDER_LEN: + s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:]) + return s + +def _common_shorten_repr(*args): + args = tuple(map(safe_repr, args)) + maxlen = max(map(len, args)) + if maxlen <= _MAX_LENGTH: + return args + + prefix = commonprefix(args) + prefixlen = len(prefix) + + common_len = _MAX_LENGTH - \ + (maxlen - prefixlen + _MIN_BEGIN_LEN + _PLACEHOLDER_LEN) + if common_len > _MIN_COMMON_LEN: + assert _MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + \ + (maxlen - prefixlen) < _MAX_LENGTH + prefix = _shorten(prefix, _MIN_BEGIN_LEN, common_len) + return tuple(prefix + s[prefixlen:] for s in args) + + prefix = _shorten(prefix, _MIN_BEGIN_LEN, _MIN_COMMON_LEN) + return tuple(prefix + _shorten(s[prefixlen:], _MIN_DIFF_LEN, _MIN_END_LEN) + for s in args) + +def safe_repr(obj, short=False): + try: + result = repr(obj) + except Exception: + result = object.__repr__(obj) + if not short or len(result) < _MAX_LENGTH: + return result + return result[:_MAX_LENGTH] + ' [truncated]...' + +def strclass(cls): + return "%s.%s" % (cls.__module__, cls.__qualname__) + +def sorted_list_difference(expected, actual): + """Finds elements in only one or the other of two, sorted input lists. + + Returns a two-element tuple of lists. The first list contains those + elements in the "expected" list but not in the "actual" list, and the + second contains those elements in the "actual" list but not in the + "expected" list. Duplicate elements in either input list are ignored. + """ + i = j = 0 + missing = [] + unexpected = [] + while True: + try: + e = expected[i] + a = actual[j] + if e < a: + missing.append(e) + i += 1 + while expected[i] == e: + i += 1 + elif e > a: + unexpected.append(a) + j += 1 + while actual[j] == a: + j += 1 + else: + i += 1 + try: + while expected[i] == e: + i += 1 + finally: + j += 1 + while actual[j] == a: + j += 1 + except IndexError: + missing.extend(expected[i:]) + unexpected.extend(actual[j:]) + break + return missing, unexpected + + +def unorderable_list_difference(expected, actual): + """Same behavior as sorted_list_difference but + for lists of unorderable items (like dicts). + + As it does a linear search per item (remove) it + has O(n*n) performance.""" + missing = [] + while expected: + item = expected.pop() + try: + actual.remove(item) + except ValueError: + missing.append(item) + + # anything left in actual is unexpected + return missing, actual + +def three_way_cmp(x, y): + """Return -1 if x < y, 0 if x == y and 1 if x > y""" + return (x > y) - (x < y) + +_Mismatch = namedtuple('Mismatch', 'actual expected value') + +def _count_diff_all_purpose(actual, expected): + 'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ' + # elements need not be hashable + s, t = list(actual), list(expected) + m, n = len(s), len(t) + NULL = object() + result = [] + for i, elem in enumerate(s): + if elem is NULL: + continue + cnt_s = cnt_t = 0 + for j in range(i, m): + if s[j] == elem: + cnt_s += 1 + s[j] = NULL + for j, other_elem in enumerate(t): + if other_elem == elem: + cnt_t += 1 + t[j] = NULL + if cnt_s != cnt_t: + diff = _Mismatch(cnt_s, cnt_t, elem) + result.append(diff) + + for i, elem in enumerate(t): + if elem is NULL: + continue + cnt_t = 0 + for j in range(i, n): + if t[j] == elem: + cnt_t += 1 + t[j] = NULL + diff = _Mismatch(0, cnt_t, elem) + result.append(diff) + return result + +def _count_diff_hashable(actual, expected): + 'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ' + # elements must be hashable + s, t = Counter(actual), Counter(expected) + result = [] + for elem, cnt_s in s.items(): + cnt_t = t.get(elem, 0) + if cnt_s != cnt_t: + diff = _Mismatch(cnt_s, cnt_t, elem) + result.append(diff) + for elem, cnt_t in t.items(): + if elem not in s: + diff = _Mismatch(0, cnt_t, elem) + result.append(diff) + return result From 62d91d844e59a02dc4b25504b37db74f03cb75ab Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 06:30:31 +0300 Subject: [PATCH 127/180] delete bakas --- .../game/python-packages/balloontip.py | 62 ------------------ .../game/python-packages/pythoncom27.dll | Bin 397824 -> 0 bytes .../python-packages/pythoncomloader27.dll | Bin 8192 -> 0 bytes .../game/python-packages/pywintypes27.dll | Bin 110592 -> 0 bytes .../game/python-packages/win32api.pyd | Bin 100864 -> 0 bytes .../game/python-packages/win32gui.pyd | Bin 167936 -> 0 bytes 6 files changed, 62 deletions(-) delete mode 100644 Monika After Story/game/python-packages/balloontip.py delete mode 100644 Monika After Story/game/python-packages/pythoncom27.dll delete mode 100644 Monika After Story/game/python-packages/pythoncomloader27.dll delete mode 100644 Monika After Story/game/python-packages/pywintypes27.dll delete mode 100644 Monika After Story/game/python-packages/win32api.pyd delete mode 100644 Monika After Story/game/python-packages/win32gui.pyd diff --git a/Monika After Story/game/python-packages/balloontip.py b/Monika After Story/game/python-packages/balloontip.py deleted file mode 100644 index c60b230def..0000000000 --- a/Monika After Story/game/python-packages/balloontip.py +++ /dev/null @@ -1,62 +0,0 @@ -# -- coding: utf-8 -- - -from win32api import * -from win32gui import * -import win32con -import sys, os -import struct -import time - -class WindowsBalloonTip: - def __init__(self): - message_map = { - win32con.WM_DESTROY: self.OnDestroy, - } - # Register the Window class. - wc = WNDCLASS() - self.hinst = wc.hInstance = GetModuleHandle(None) - wc.lpszClassName = "PythonTaskbar" - wc.lpfnWndProc = message_map # could also specify a wndproc. - self.classAtom = RegisterClass(wc) - - def showWindow(self,title, msg): - # Create the Window. - style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU - self.hwnd = CreateWindow( self.classAtom, "Taskbar", style, \ - 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, \ - 0, 0, self.hinst, None) - UpdateWindow(self.hwnd) - - #Get/Set Notification Icon - iconPathName = os.path.abspath(os.path.join( sys.path[0], "CustomIconWindows.ico" )) - icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE - try: - hicon = LoadImage(self.hinst, iconPathName, \ - win32con.IMAGE_ICON, 0, 0, icon_flags) - except: - hicon = LoadIcon(0, win32con.IDI_APPLICATION) - - #Initialize the notif itself - flags = NIF_ICON | NIF_MESSAGE | NIF_TIP - nid = (self.hwnd, 0, flags, win32con.WM_USER+20, hicon, "Monika After Story") - - try: - Shell_NotifyIcon(NIM_ADD, nid) - Shell_NotifyIcon(NIM_MODIFY, \ - (self.hwnd, 0, NIF_INFO, win32con.WM_USER+20,\ - hicon, "Balloon tooltip",msg,200,title)) - - #If we got here, that means we had no issue making the notif - return True - - except: - #Something went wrong, need to flag this to not make a sound - return False - - def OnDestroy(self, hwnd, msg, wparam, lparam): - nid = (self.hwnd, 0) - Shell_NotifyIcon(NIM_DELETE, nid) - PostQuitMessage(0) # Terminate the app. - -def balloon_tip(title, msg): - w=WindowsBalloonTip(msg, title) diff --git a/Monika After Story/game/python-packages/pythoncom27.dll b/Monika After Story/game/python-packages/pythoncom27.dll deleted file mode 100644 index d612171d332a53d836fed96bf98bde0238ab27a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 397824 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 z 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 "you are" -#---------------------------------------------------------------------- -gReflections = { - "am" : "are", - "was" : "were", - "i" : "you", - "i'd" : "you would", - "i've" : "you have", - "i'll" : "you will", - "my" : "your", - "are" : "am", - "you've": "I have", - "you'll": "I will", - "your" : "my", - "yours" : "mine", - "you" : "me", - "me" : "you" -} - -#---------------------------------------------------------------------- -# gPats, the main response table. Each element of the list is a -# two-element list; the first is a regexp, and the second is a -# list of possible responses, with group-macros labelled as -# %1, %2, etc. -#---------------------------------------------------------------------- -gPats = [ - [r'I need (.*)', - [ "Why do you need %1?", - "Would it really help you to get %1?", - "Are you sure you need %1?"]], - - [r'Why don\'?t you ([^\?]*)\??', - [ "Do you really think I don't %1?", - "Perhaps eventually I will %1.", - "Do you really want me to %1?"]], - - [r'Why can\'?t I ([^\?]*)\??', - [ "Do you think you should be able to %1?", - "If you could %1, what would you do?", - "I don't know -- why can't you %1?", - "Have you really tried?"]], - - [r'I can\'?t (.*)', - [ "How do you know you can't %1?", - "Perhaps you could %1 if you tried.", - "What would it take for you to %1?"]], - - [r'I am (.*)', - [ "Did you come to me because you are %1?", - "How long have you been %1?", - "How do you feel about being %1?"]], - - [r'I\'?m (.*)', - [ "How does being %1 make you feel?", - "Do you enjoy being %1?", - "Why do you tell me you're %1?", - "Why do you think you're %1?"]], - - [r'Are you ([^\?]*)\??', - [ "Why does it matter whether I am %1?", - "Would you prefer it if I were not %1?", - "Perhaps you believe I am %1.", - "I may be %1 -- what do you think?"]], - - [r'What (.*)', - [ "Why do you ask?", - "How would an answer to that help you?", - "What do you think?"]], - - [r'How (.*)', - [ "How do you suppose?", - "Perhaps you can answer your own question.", - "What is it you're really asking?"]], - - [r'Because (.*)', - [ "Is that the real reason?", - "What other reasons come to mind?", - "Does that reason apply to anything else?", - "If %1, what else must be true?"]], - - [r'(.*) sorry (.*)', - [ "There are many times when no apology is needed.", - "What feelings do you have when you apologize?"]], - - [r'Hello(.*)', - [ "Hello... I'm glad you could drop by today.", - "Hi there... how are you today?", - "Hello, how are you feeling today?"]], - - [r'I think (.*)', - [ "Do you doubt %1?", - "Do you really think so?", - "But you're not sure %1?"]], - - [r'(.*) friend (.*)', - [ "Tell me more about your friends.", - "When you think of a friend, what comes to mind?", - "Why don't you tell me about a childhood friend?"]], - - [r'Yes', - [ "You seem quite sure.", - "OK, but can you elaborate a bit?"]], - - [r'(.*) computer(.*)', - [ "Are you really talking about me?", - "Does it seem strange to talk to a computer?", - "How do computers make you feel?", - "Do you feel threatened by computers?"]], - - [r'Is it (.*)', - [ "Do you think it is %1?", - "Perhaps it's %1 -- what do you think?", - "If it were %1, what would you do?", - "It could well be that %1."]], - - [r'It is (.*)', - [ "You seem very certain.", - "If I told you that it probably isn't %1, what would you feel?"]], - - [r'Can you ([^\?]*)\??', - [ "What makes you think I can't %1?", - "If I could %1, then what?", - "Why do you ask if I can %1?"]], - - [r'Can I ([^\?]*)\??', - [ "Perhaps you don't want to %1.", - "Do you want to be able to %1?", - "If you could %1, would you?"]], - - [r'You are (.*)', - [ "Why do you think I am %1?", - "Does it please you to think that I'm %1?", - "Perhaps you would like me to be %1.", - "Perhaps you're really talking about yourself?"]], - - [r'You\'?re (.*)', - [ "Why do you say I am %1?", - "Why do you think I am %1?", - "Are we talking about you, or me?"]], - - [r'I don\'?t (.*)', - [ "Don't you really %1?", - "Why don't you %1?", - "Do you want to %1?"]], - - [r'I feel (.*)', - [ "Good, tell me more about these feelings.", - "Do you often feel %1?", - "When do you usually feel %1?", - "When you feel %1, what do you do?"]], - - [r'I have (.*)', - [ "Why do you tell me that you've %1?", - "Have you really %1?", - "Now that you have %1, what will you do next?"]], - - [r'I would (.*)', - [ "Could you explain why you would %1?", - "Why would you %1?", - "Who else knows that you would %1?"]], - - [r'Is there (.*)', - [ "Do you think there is %1?", - "It's likely that there is %1.", - "Would you like there to be %1?"]], - - [r'My (.*)', - [ "I see, your %1.", - "Why do you say that your %1?", - "When your %1, how do you feel?"]], - - [r'You (.*)', - [ "We should be discussing you, not me.", - "Why do you say that about me?", - "Why do you care whether I %1?"]], - - [r'Why (.*)', - [ "Why don't you tell me the reason why %1?", - "Why do you think %1?" ]], - - [r'I want (.*)', - [ "What would it mean to you if you got %1?", - "Why do you want %1?", - "What would you do if you got %1?", - "If you got %1, then what would you do?"]], - - [r'(.*) mother(.*)', - [ "Tell me more about your mother.", - "What was your relationship with your mother like?", - "How do you feel about your mother?", - "How does this relate to your feelings today?", - "Good family relations are important."]], - - [r'(.*) father(.*)', - [ "Tell me more about your father.", - "How did your father make you feel?", - "How do you feel about your father?", - "Does your relationship with your father relate to your feelings today?", - "Do you have trouble showing affection with your family?"]], - - [r'(.*) child(.*)', - [ "Did you have close friends as a child?", - "What is your favorite childhood memory?", - "Do you remember any dreams or nightmares from childhood?", - "Did the other children sometimes tease you?", - "How do you think your childhood experiences relate to your feelings today?"]], - - [r'(.*)\?', - [ "Why do you ask that?", - "Please consider whether you can answer your own question.", - "Perhaps the answer lies within yourself?", - "Why don't you tell me?"]], - - [r'quit', - [ "Thank you for talking with me.", - "Good-bye.", - "Thank you, that will be $150. Have a good day!"]], - - [r'(.*)', - [ "Please tell me more.", - "Let's change focus a bit... Tell me about your family.", - "Can you elaborate on that?", - "Why do you say that %1?", - "I see.", - "Very interesting.", - "%1.", - "I see. And what does that tell you?", - "How does that make you feel?", - "How do you feel when you say that?"]] - ] - -#---------------------------------------------------------------------- -# command_interface -#---------------------------------------------------------------------- -def command_interface(): - print('Therapist\n---------') - print('Talk to the program by typing in plain English, using normal upper-') - print('and lower-case letters and punctuation. Enter "quit" when done.') - print('='*72) - print('Hello. How are you feeling today?') - - s = '' - therapist = eliza(); - while s != 'quit': - try: - s = input('> ') - except EOFError: - s = 'quit' - print(s) - while s[-1] in '!.': - s = s[:-1] - print(therapist.respond(s)) - - -if __name__ == "__main__": - command_interface() diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index f0e0e069a5..192d3dc656 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -311,14 +311,12 @@ define MAS_PRONOUN_GENDER_MAP = { init python: import subprocess import os - import eliza # mod specific - import datetime # mod specific - import battery # mod specific + import datetime + import battery import re import store.songs as songs import store.hkb_button as hkb_button import store.mas_globals as mas_globals - therapist = eliza.eliza() process_list = [] currentuser = None # start if with no currentuser if renpy.windows: From fc9da148013947ce83037d558c0e87d2f6d21c03 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 23:39:29 +0300 Subject: [PATCH 145/180] battery never worked anyway --- .../game/python-packages/battery/__init__.py | 112 ---------- .../game/python-packages/battery/linux.py | 69 ------ .../game/python-packages/battery/misc.py | 197 ------------------ .../game/python-packages/battery/windows.py | 95 --------- Monika After Story/game/script-ch30.rpy | 14 -- Monika After Story/game/script-topics.rpy | 59 +++--- 6 files changed, 29 insertions(+), 517 deletions(-) delete mode 100644 Monika After Story/game/python-packages/battery/__init__.py delete mode 100644 Monika After Story/game/python-packages/battery/linux.py delete mode 100644 Monika After Story/game/python-packages/battery/misc.py delete mode 100644 Monika After Story/game/python-packages/battery/windows.py diff --git a/Monika After Story/game/python-packages/battery/__init__.py b/Monika After Story/game/python-packages/battery/__init__.py deleted file mode 100644 index 20f890486c..0000000000 --- a/Monika After Story/game/python-packages/battery/__init__.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -This module provides functions to check information for the battery and -the AC Line for supported systems. -""" - -# known issues/help needed: -# Running through wine makes it impossible to detect the battery -# The Darwin linux distro conflicts with the OSX battery detection -# The battery doesn't get properly detected on some linux distros -# Testing on linux distros, like seriously, a lot of testing - -import platform - -from . import windows, linux, misc - -""" -List of functions to check the battery level. -""" -BATTERY_LEVEL_FUNCTIONS = { - 'Windows': windows.get_level, - 'Linux': linux.get_level, - '*': misc.get_level, -} - -""" -List of functions to check if the battery is present. -""" -BATTERY_CHECK_FUNCTIONS = { - 'Windows': windows.is_battery_present, - 'Linux': linux.is_battery_present, - '*': misc.is_battery_present, -} - -""" -List of functions to check if the system is charging. -""" -AC_LINE_CHECK_FUNCTIONS = { - 'Windows': windows.is_charging, - 'Linux': linux.is_charging, - '*': misc.is_charging, -} - -_system = platform.system() - - -def _run_function_by_system(funcdict): - """ - Executes a function based on the system running. - - The '*' will be used for others, usually misc module's functions. - - Raises NotImplemetedError if the system is unsupported. - - :param funcdict: Dictionary of functions by system - """ - if _system in BATTERY_LEVEL_FUNCTIONS: - func = funcdict[_system] - elif misc.can_check(): - func = funcdict['*'] - else: - return None - - return func() - - -def get_level(): - """ - Return the system battery level, otherwise None if the system - doesn't have any batteries. - """ - try: - return _run_function_by_system(BATTERY_LEVEL_FUNCTIONS) - except: - return None - - -def is_battery_present(): - """ - Check if the system has a battery present. - """ - try: - return _run_function_by_system(BATTERY_CHECK_FUNCTIONS) - except: - return False - - -def is_charging(): - """ - Check if the system is charging. - """ - try: - return _run_function_by_system(AC_LINE_CHECK_FUNCTIONS) - except: - return False - - -def get_supported_systems(): - """ - Returns a list of supported systems. - """ - systems = [x for x in BATTERY_LEVEL_FUNCTIONS if x != '*'] - systems += misc.get_supported_systems() - - return systems - - -def is_supported(): - """ - Check if this system is supported. - """ - return False -# return _system in get_supported_systems() diff --git a/Monika After Story/game/python-packages/battery/linux.py b/Monika After Story/game/python-packages/battery/linux.py deleted file mode 100644 index 259b74f352..0000000000 --- a/Monika After Story/game/python-packages/battery/linux.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -This module provides functions to check information for the battery and -the AC Line on Linux systems. -""" - -import os - -""" -Path to the Linux power supply class directory -""" -LINUX_POWER_SUPPLY_CLASS = '/sys/class/power_supply/' - - -def _get_battery(): - """ - Return the path to the systems' battery class, None otherwise. - """ - for file in os.listdir(LINUX_POWER_SUPPLY_CLASS): - if file.startswith('BAT'): - return os.path.join(LINUX_POWER_SUPPLY_CLASS, file) - - return None - - -def _read_battery(what): - """ - Return content from a file in the battery class. - """ - bat = _get_battery() - - if bat == None: - raise RuntimeError('Battery not found') - - path = os.path.join(bat, what) - - with open(path, 'r') as f: - content = f.read() - - return content - - -def get_level(): - """ - Return the system battery level from the battery class in percentage, - otherwise None if the system doesn't have any batteries. - """ - level = int(_read_battery('capacity')) - - return level - - -def is_charging(): - """ - Check if the system is charging based on the battery class - - :return: True if it's charging, false otherwise - """ - status = _read_battery('status') - - return status == 'Charging\n' - - -def is_battery_present(): - """ - Check if the system have a battery present - - :return: True if there's a battery, false otherwise - """ - return _get_battery() != None diff --git a/Monika After Story/game/python-packages/battery/misc.py b/Monika After Story/game/python-packages/battery/misc.py deleted file mode 100644 index cfea00f6a4..0000000000 --- a/Monika After Story/game/python-packages/battery/misc.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -This module provides functions to check information for the battery and -the AC Line for various systems by running commands onto a shell. -""" - -import platform -import re -import subprocess - -""" -List of commands that can be used to check for the battery level. -""" -BATTERY_LEVEL_COMMANDS = { - 'FreeBSD': 'sysctl hw.acpi.battery.life', - 'Darwin': 'pmset -g batt', -} - -""" -List of regex patterns to be used with BATTERY_LEVEL_COMMANDS to extract -the battery level -""" -BATTERY_LEVEL_REGEX = { - 'FreeBSD': r'hw\.acpi\.battery\.life: (\d+)', - 'Darwin': r'(\d+%)', -} - -""" -List of commands that can be used to check for the AC Line status. -""" -AC_LINE_CHECK_COMMANDS = { - 'FreeBSD': 'sysctl hw.acpi.acline', - 'Darwin': 'pmset -g batt', -} - -""" -List of regex patterns to be used with AC_LINE_CHECK_COMMANDS to extract -the AC Line status. -""" -AC_LINE_CHECK_REGEX = { - 'FreeBSD': r'hw\.acpi\.acline: (\d)', - 'Darwin': r'\d+%; (\w+|AC attached);' -} - -_system = platform.system() - - -class RegexDidNotMatchError(RuntimeError): - pass - - -def _run_command_based_by_system(cmddict, regexdict): - """ - Runs a command based on the system running. - - Raises RegexDidNotMatchError if the regex provided did not match - the output of the command. - - :param cmddict: Dictionary of commands to run by system - :param regexdict: Dictionary of regex patterns to filter the output - by system - - :return: The output from the command filtered by the regex pattern - if provided - """ - cmd = cmddict[_system] - - output = subprocess.check_output(cmd, shell=True) - - if _system in regexdict: - regex = regexdict[_system] - res = re.search(regex, output) - if res: - output = res.group(1) - else: - raise RegexDidNotMatchError("Regex failure for '%s' on %s" % - (output, _system)) - - return output - - -def _run_function_based_by_system(funcdict): - """ - Executes a function based on the system running - - :param funcdict: Dictionary of functions by system - """ - func = funcdict[_system] - - return func() - - -def get_level(): - """ - Return the system battery level based on the command output in percentage, - otherwise None if the system doesn't have any batteries. - """ - try: - output = _run_command_based_by_system(BATTERY_LEVEL_COMMANDS, - BATTERY_LEVEL_REGEX) - value = int(output.rstrip().replace('%','')) - except RegexDidNotMatchError: - return None - - return int(output.rstrip().replace('%','')) - - -def get_supported_systems(): - """ - Returns a list of supported systems. - """ - return BATTERY_LEVEL_COMMANDS.keys() - - -def can_check(): - """ - Check if this module can check the battery. - """ - return _system in get_supported_systems() - - -def _freebsd_is_battery_present(): - """ - Check if there's a battery present for FreeBSD systems. - """ - try: - level = get_level() - except RegexDidNotMatchError: - return False - return level != -1 - - -def _darwin_is_battery_present(): - """ - Check if there's a battery present for macOS systems. - """ - try: - get_level() - except RegexDidNotMatchError: - return False - return True - - -""" -List of functions that can be used to check if the battery is present. -""" -BATTERY_CHECK_FUNCTIONS = { - 'FreeBSD': _freebsd_is_battery_present, - 'Darwin': _darwin_is_battery_present, -} - - -def is_battery_present(): - """ - Check if the system has a battery present. - """ - return _run_function_based_by_system(BATTERY_CHECK_FUNCTIONS) - - -def _get_ac_line_status(): - """ - Return the output of the AC Line command - """ - output = _run_command_based_by_system(AC_LINE_CHECK_COMMANDS, - AC_LINE_CHECK_REGEX) - return output - - -def _freebsd_is_charging(): - """ - Check if it's charging for FreeBSD systems. - """ - return bool(int(_get_ac_line_status().rstrip())) - - -def _darwin_is_charging(): - """ - Check if it's charging for macOS systems. - """ - charging_statuses = ['charging', 'charged'] - - return _get_ac_line_status() in charging_statuses - - -""" -List of functions that can be used to check if the system is charging. -""" -AC_LINE_CHECK_FUNCTIONS = { - 'FreeBSD': _freebsd_is_charging, - 'Darwin': _darwin_is_charging, -} - - -def is_charging(): - """ - Check if the system is charging. - """ - return _run_function_based_by_system(AC_LINE_CHECK_FUNCTIONS) diff --git a/Monika After Story/game/python-packages/battery/windows.py b/Monika After Story/game/python-packages/battery/windows.py deleted file mode 100644 index ff53d28955..0000000000 --- a/Monika After Story/game/python-packages/battery/windows.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -This module provides functions to check information for the battery and -the AC Line on Windows systems. - -It may only work on Windows Server 2003 / Windows XP and above. - -For more information see the following: -https://msdn.microsoft.com/en-us/library/windows/desktop/aa373232(v=vs.85).aspx -""" - -import ctypes - -try: - from ctypes import wintypes -except: # pragma: no cover - wintypes = None - - -def _system_power_status(): - """ - Return 'SYSTEM_POWER_STATUS' C structure with the values set by - GetSystemPowerStatus. - """ - - class SYSTEM_POWER_STATUS(ctypes.Structure): - """ - This class is a representation of the SYSTEM_POWER_STATUS C structure - used by GetSystemPowerStatus. - """ - _fields_ = [ - ('ACLineStatus', wintypes.BYTE), - ('BatteryFlag', wintypes.BYTE), - ('BatteryLifePercent', wintypes.BYTE), - ('Reserved1', wintypes.BYTE), - ('BatteryLifeTime', wintypes.DWORD), - ('BatteryFullLifeTime', wintypes.DWORD), - ] - - pointer = ctypes.POINTER(SYSTEM_POWER_STATUS) - - GetSystemPowerStatus = ctypes.windll.kernel32.GetSystemPowerStatus - GetSystemPowerStatus.argtypes = [pointer] - GetSystemPowerStatus.restype = wintypes.BOOL - - status = SYSTEM_POWER_STATUS() - - if not GetSystemPowerStatus(ctypes.pointer(status)): - return None - - return status - - -def _get_ac_status(): - """ - Return the ACLineStatus from SYSTEM_POWER_STATUS - """ - return _system_power_status().ACLineStatus - - -def _get_battery_flag(): - """ - Return the BatteryFlag from SYSTEM_POWER_STATUS - """ - return _system_power_status().BatteryFlag - - -def get_level(): - """ - Return the system battery level in percentage, otherwise None - if the value is unknown - """ - percentage = _system_power_status().BatteryLifePercent - - if percentage == 255: - return None - - return percentage - - -def is_charging(): - """ - Check if the system is charging based on the ACLineStatus. - - :return: True if it's charging, false otherwise - """ - return _get_ac_status() == 1 or _get_ac_status() == 255 - - -def is_battery_present(): - """ - Check if the system have a battery present - - :return: True if there's a battery, false otherwise - """ - return (_get_battery_flag() != 128 or _get_battery_flag() != 255) and _get_battery_flag() != -1 diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 192d3dc656..9e571db951 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -11,7 +11,6 @@ default persistent.first_run = True default persistent.rejected_monika = None default initial_monika_file_check = None define modoorg.CHANCE = 20 -define mas_battery_supported = False define mas_in_intro_flow = False # True means disable animations, False means enable @@ -312,7 +311,6 @@ init python: import subprocess import os import datetime - import battery import re import store.songs as songs import store.hkb_button as hkb_button @@ -349,9 +347,6 @@ init python: else: mcname = persistent.mcname - # check for battery support - mas_battery_supported = battery.is_supported() - # we need a new music channel for background audio (like rain!) # this uses the amb (ambient) mixer. renpy.music.register_channel( @@ -1564,15 +1559,6 @@ label ch30_post_mid_loop_eval: window auto -# python: -# if ( -# mas_battery_supported -# and battery.is_battery_present() -# and not battery.is_charging() -# and battery.get_level() < 20 -# ): -# pushEvent("monika_battery") - if ( store.mas_globals.in_idle_mode or ( diff --git a/Monika After Story/game/script-topics.rpy b/Monika After Story/game/script-topics.rpy index d1c4fc2a61..09597ba6de 100644 --- a/Monika After Story/game/script-topics.rpy +++ b/Monika After Story/game/script-topics.rpy @@ -8,7 +8,6 @@ define mas_rev_unseen = [] define mas_rev_seen = [] define mas_rev_mostseen = [] define testitem = 0 -define mas_did_monika_battery = False define mas_sensitive_limit = 3 init -2 python in mas_topics: @@ -8418,35 +8417,35 @@ label monika_wolf: m 1ekbsa "You're my [hero] after all~" return -label monika_battery: - if mas_did_monika_battery: - jump monika_close_game_battery - else: - jump monika_complain_battery - -label monika_complain_battery: - $ mas_did_monika_battery = True - m 1euc "Umm, [player]..." - m 1eua "It looks like your computer's battery is about to run out..." - m 1eka "Can you charge it for me?" - m 1lksdlc "I don't want us to be separated, or worse..." - m 2ekc "It'd be really unpleasant for me if I suddenly lose consciousness." - m 2eka "So please charge your computer, okay?" - m 3eka "...or at least let me know when you're going." - m 1hua "Thank you, [player]~" - return - -label monika_close_game_battery: - $ mas_loseAffection() - m 1lksdlc "[player]..." - m 1ekc "I'm sorry, but I'm gonna have to close the game before the battery runs out." - m 3eka "So...I'll just close the game for now until you can charge your computer.{w=3.0} {nw}" - - $ is_charging = battery.is_charging() - if is_charging: - jump monika_system_charging - $ persistent.closed_self = True - jump _quit +# label monika_battery: +# if mas_did_monika_battery: +# jump monika_close_game_battery +# else: +# jump monika_complain_battery + +# label monika_complain_battery: +# $ mas_did_monika_battery = True +# m 1euc "Umm, [player]..." +# m 1eua "It looks like your computer's battery is about to run out..." +# m 1eka "Can you charge it for me?" +# m 1lksdlc "I don't want us to be separated, or worse..." +# m 2ekc "It'd be really unpleasant for me if I suddenly lose consciousness." +# m 2eka "So please charge your computer, okay?" +# m 3eka "...or at least let me know when you're going." +# m 1hua "Thank you, [player]~" +# return + +# label monika_close_game_battery: +# $ mas_loseAffection() +# m 1lksdlc "[player]..." +# m 1ekc "I'm sorry, but I'm gonna have to close the game before the battery runs out." +# m 3eka "So...I'll just close the game for now until you can charge your computer.{w=3.0} {nw}" + +# $ is_charging = battery.is_charging() +# if is_charging: +# jump monika_system_charging +# $ persistent.closed_self = True +# jump _quit label monika_system_charging: $ mas_gainAffection() From 96dec3bfbe5be09018525ead87b06963ff0e8a37 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:14:05 +0300 Subject: [PATCH 146/180] fix logs newlines either use a proper text editor, or don't read logs if this doesn't work --- Monika After Story/game/0utils.rpy | 32 ++++++++++------------ Monika After Story/game/zz_spritejsons.rpy | 14 ++++------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Monika After Story/game/0utils.rpy b/Monika After Story/game/0utils.rpy index f2ebb470f2..b4a484c3b6 100644 --- a/Monika After Story/game/0utils.rpy +++ b/Monika After Story/game/0utils.rpy @@ -51,16 +51,17 @@ python early in mas_logging: if datefmt is None: datefmt = DEF_DATEFMT - super(MASLogFormatter, self).__init__(fmt=fmt, datefmt=datefmt) + super().__init__(fmt=fmt, datefmt=datefmt) def format(self, record): """ Override of format - mainly replaces the levelname prop """ self.update_levelname(record) - return self.replace_lf( - super(MASLogFormatter, self).format(record) - ) + # return self.replace_lf( + # super().format(record) + # ) + return super().format(record) def update_levelname(self, record): """ @@ -69,12 +70,12 @@ python early in mas_logging: """ record.levelname = LT_MAP.get(record.levelno, record.levelname) - @classmethod - def replace_lf(cls, msg): - """ - Replaces all line feeds with carriage returns and a line feed - """ - return re.sub(cls.NEWLINE_MATCHER, cls.LINE_TERMINATOR, msg) + # @classmethod + # def replace_lf(cls, msg): + # """ + # Replaces all line feeds with carriage returns and a line feed + # """ + # return re.sub(cls.NEWLINE_MATCHER, cls.LINE_TERMINATOR, msg) class MASNewlineLogFormatter(MASLogFormatter): """ @@ -103,11 +104,9 @@ python early in mas_logging: """ Applies a prefix newline if appropriate. """ - return self.replace_lf( - self.apply_newline_prefix( - record, - super(MASNewlineLogFormatter, self).format(record) - ) + return self.apply_newline_prefix( + record, + super().format(record) ) @@ -206,7 +205,7 @@ python early in mas_logging: #Add the header to each log, including OS info + MAS version number #NOTE: python logging does not auto handle CRLF, so we need to explicitly manage that for the header - LOG_HEADER = "\r\n\r\n{_date}\r\n{system_info}\r\n{renpy_ver}\r\n\r\nVERSION: {game_ver}\r\n{separator}" + LOG_HEADER = "\n\n{_date}\n{system_info}\n{renpy_ver}\n\nVERSION: {game_ver}\n{separator}" #Unformatted logs use these consts (spj/pnm) MSG_INFO = "[" + LT_INFO + "]: {0}" @@ -244,7 +243,6 @@ python early in mas_logging: (Default: True) formatter - custom logging.Formatter to be used. If None is provided, the default MASLogFormatter is used. - NOTE: IF YOU ARE USING YOUR OWN FORMATTER, YOU SHOULD CALL THE `replace_lf` METHOD TO ENSURE YOUR LOGS ARE USING CRLF (Default: None) adapter_ctor - Constructor reference to the adapter we want to use. If None, no adapter is used (Default: None) diff --git a/Monika After Story/game/zz_spritejsons.rpy b/Monika After Story/game/zz_spritejsons.rpy index 58716c7c49..9da646be38 100644 --- a/Monika After Story/game/zz_spritejsons.rpy +++ b/Monika After Story/game/zz_spritejsons.rpy @@ -448,14 +448,12 @@ init -21 python in mas_sprites_json: RETURNS: string to be logged """ self.update_levelname(record) - return self.replace_lf( - self.apply_newline_prefix( - record, - "[{0}]: {1}{2}".format( - record.levelname, - " " * (record.indent_lvl * 2), - record.msg - ) + return self.apply_newline_prefix( + record, + "[{0}]: {1}{2}".format( + record.levelname, + " " * (record.indent_lvl * 2), + record.msg ) ) From e9701350e6312f4a453ad8b116afb4661eaeba02 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 16:58:36 +0300 Subject: [PATCH 147/180] fix wr crash, add to blacklist, improve code --- .../game/script-windowreacts.rpy | 149 ++++++------------ 1 file changed, 47 insertions(+), 102 deletions(-) diff --git a/Monika After Story/game/script-windowreacts.rpy b/Monika After Story/game/script-windowreacts.rpy index ced146bc35..19033adcba 100644 --- a/Monika After Story/game/script-windowreacts.rpy +++ b/Monika After Story/game/script-windowreacts.rpy @@ -1,17 +1,22 @@ +init python: + # 99% of the wr use the same set of event rules + # let's reuse them + __DEFAULT_WR_RULES = { + "notif-group": "Window Reactions", + "skip alert": None, + "keep_idle_exp": None, + "skip_pause": None + } init 5 python: addEvent( Event( persistent._mas_windowreacts_database, eventlabel="mas_wrs_pinterest", category=["Pinterest"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -37,14 +42,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_duolingo", category=["Duolingo"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -70,14 +71,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_wikipedia", category=["- Wikipedia"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -121,14 +118,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_virtualpiano", category=["^Virtual Piano"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -158,14 +151,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_youtube", category=["- YouTube"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -185,21 +174,21 @@ label mas_wrs_youtube: return init 5 python: + rules = dict(__DEFAULT_WR_RULES) + rules.pop("keep_idle_exp") addEvent( Event( persistent._mas_windowreacts_database, eventlabel="mas_wrs_r34m", category=[r"(?i)(((r34|rule\s?34).*monika)|(post \d+:[\w\s]+monika)|(monika.*(r34|rule\s?34)))"], aff_range=(mas_aff.AFFECTIONATE, None), - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "skip_pause": None - }, + rules=rules, show_in_idle=True ), + restartBlacklist=True, code="WRS" ) + del rules label mas_wrs_r34m: python: @@ -238,14 +227,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_monikamoddev", category=["MonikaModDev"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -270,14 +255,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_twitter", category=["/ Twitter"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -292,7 +273,7 @@ label mas_wrs_twitter: "Anything interesting to share, [player]?": False, "280 characters? I only need [temp_len]...\n[temp_line]": True } - quip = renpy.random.choice(ily_quips_map.keys()) + quip = renpy.random.choice(tuple(ily_quips_map.keys())) wrs_success = mas_display_notif( m_name, @@ -312,14 +293,10 @@ label mas_wrs_twitter: # persistent._mas_windowreacts_database, # eventlabel="mas_wrs_monikatwitter", # category=['twitter', 'lilmonix3'], -# rules={ -# "notif-group": "Window Reactions", -# "skip alert": None, -# "keep_idle_exp": None, -# "skip_pause": None -# }, +# rules=dict(__DEFAULT_WR_RULES), # show_in_idle=True # ), +# restartBlacklist=True, # code="WRS" # ) @@ -345,14 +322,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_4chan", category=["- 4chan"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -380,14 +353,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_pixiv", category=["- pixiv"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -429,14 +398,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_reddit", category=[r"(?i)reddit"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -462,14 +427,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_mal", category=["MyAnimeList"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -496,14 +457,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_deviantart", category=["DeviantArt"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -528,14 +485,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_netflix", category=["Netflix"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -561,14 +514,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_twitch", category=["- Twitch"], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) @@ -594,14 +543,10 @@ init 5 python: persistent._mas_windowreacts_database, eventlabel="mas_wrs_word_processor", category=['Google Docs|LibreOffice Writer|Microsoft Word'], - rules={ - "notif-group": "Window Reactions", - "skip alert": None, - "keep_idle_exp": None, - "skip_pause": None - }, + rules=dict(__DEFAULT_WR_RULES), show_in_idle=True ), + restartBlacklist=True, code="WRS" ) From d1efbb74e83a4d06cbfde0c8212f183d845574e4 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 17:00:49 +0300 Subject: [PATCH 148/180] handle the `None` case --- Monika After Story/game/zz_windowutils.rpy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index a8c0edce49..151c3a6edf 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -261,7 +261,8 @@ init python in mas_windowutils: ASSUMES: OS IS WINDOWS (renpy.windows) """ try: - return winnie32api.get_active_window_title() + # winnie32api can return None + return winnie32api.get_active_window_title() or "" except Exception: return "" From 99ec6011703f458b435fb294d4d5fbee8f849010 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:01:38 +0300 Subject: [PATCH 149/180] update `winnie32api` --- .../python-packages/winnie32api/__init__.py | 4 +- .../python-packages/winnie32api/windows.py | 91 ++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/python-packages/winnie32api/__init__.py b/Monika After Story/game/python-packages/winnie32api/__init__.py index effa811858..97ffb1dcbc 100644 --- a/Monika After Story/game/python-packages/winnie32api/__init__.py +++ b/Monika After Story/game/python-packages/winnie32api/__init__.py @@ -8,7 +8,7 @@ __title__ = "winnie32api" __author__ = "Booplicate" -__version__ = "0.0.3" +__version__ = "0.0.4" import ctypes @@ -19,6 +19,8 @@ get_hwnd_by_title, get_window_title, get_window_rect, + flash_window, + unflash_window, get_active_window_hwnd, get_active_window_title, get_active_window_rect diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py index 07c17c8550..a545d26674 100644 --- a/Monika After Story/game/python-packages/winnie32api/windows.py +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -6,7 +6,7 @@ ) from .common import ( - HWND, + # HWND, Rect, Pack, WinAPIError, @@ -22,6 +22,32 @@ WNDENUMPROC = ctypes.WINFUNCTYPE(wt.BOOL, wt.HWND, wt.LPARAM) +class FlashWInfo(ctypes.Structure): + _fields_ = [ + ("cbSize", wt.UINT), + ("hwnd", wt.HWND), + ("dwFlags", wt.DWORD), + ("uCount", wt.UINT), + ("dwTimeout", wt.DWORD) + ] + +class FLASHW(): + """ + 0x00000003. Flash both the window caption and taskbar button. + 0x00000001. Flash the window caption. + 0. Stop flashing. The system restores the window to its original state. + 0x00000004. Flash continuously, until the FLASHW_STOP flag is set. + 0x0000000C. Flash continuously until the window comes to the foreground. + 0x00000002. Flash the taskbar button. + """ + ALL = 0x00000003 + CAPTION = 0x00000001 + STOP = 0 + TIMER = 0x00000004 + TIMERNOFG = 0x0000000C + TRAY = 0x00000002 + + user32.IsWindowVisible.argtypes = (wt.HWND,) user32.IsWindowVisible.restype = wt.BOOL @@ -37,11 +63,14 @@ user32.GetWindowRect.argtypes = (wt.HWND, wt.LPRECT) user32.GetWindowRect.restype = wt.BOOL +user32.FlashWindowEx.argtypes = (ctypes.POINTER(FlashWInfo),) +user32.FlashWindowEx.restype = wt.BOOL + user32.GetForegroundWindow.argtypes = () user32.GetForegroundWindow.restype = wt.HWND -def get_hwnd_by_title(title: str) -> Optional[HWND]: +def get_hwnd_by_title(title: str) -> Optional[int]: """ Returns first window hwnd with the given title """ @@ -61,7 +90,7 @@ def callback(hwnd: int, lparam: int) -> bool: user32.EnumWindows(WNDENUMPROC(callback), wt.LPARAM(0)) return pack.value -def get_window_title(hwnd: HWND) -> str: +def get_window_title(hwnd: int) -> str: """ Returns a window title as a str """ @@ -86,7 +115,7 @@ def get_window_title(hwnd: HWND) -> str: return buffer.value -def get_window_rect(hwnd: HWND) -> Rect: +def get_window_rect(hwnd: int) -> Rect: """ Returns a window rect """ @@ -97,8 +126,60 @@ def get_window_rect(hwnd: HWND) -> Rect: return Rect.from_coords(c_rect.left, c_rect.top, c_rect.right, c_rect.bottom) +def flash_window( + hwnd: int, + count: Optional[int] = 1, + caption: bool = True, + tray: bool = True +): + """ + Flashes a window + + IN: + hwnd - the window hwnd + coutn - the number of flashes + -1 means flash infinitely until asked to stop + None means flash infinitely until the window becomes focused + caption - do we flash window caption + tray - do weflash tray icon + + OUT: + bool - success status + """ + flash_info = FlashWInfo() + flash_info.cbSize = ctypes.sizeof(flash_info) + flash_info.hwnd = hwnd + + flags = 0 + if caption: + flags |= FLASHW.CAPTION + if tray: + flags |= FLASHW.TRAY + if count is None: + flags |= FLASHW.TIMERNOFG + if count == -1: + flags |= FLASHW.TIMER + + flash_info.dwFlags = flags + flash_info.uCount = count + flash_info.dwTimeout = 0 + + user32.FlashWindowEx(ctypes.byref(flash_info)) + +def unflash_window(hwnd: int): + """ + Stops window flashing + """ + flash_info = FlashWInfo() + flash_info.cbSize = ctypes.sizeof(flash_info) + flash_info.hwnd = hwnd + flash_info.dwFlags = FLASHW.STOP + flash_info.uCount = 0 + flash_info.dwTimeout = 0 + user32.FlashWindowEx(ctypes.byref(flash_info)) + -def get_active_window_hwnd() -> Optional[HWND]: +def get_active_window_hwnd() -> Optional[int]: """ Returns active window title hwnd (id) """ From 495184d9654cc35fc60c67789753912ce11d76fb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:06:51 +0300 Subject: [PATCH 150/180] smol cleanup --- Monika After Story/game/definitions.rpy | 4 +-- Monika After Story/game/zz_windowutils.rpy | 39 +++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index ad9fde1825..42bcb87479 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -47,11 +47,11 @@ python early: ### Overrides of core renpy things - def dummy(*args, **kwargs): + def dummy(*args, **kwargs) -> None: """ Dummy function that does nothing """ - return + return None class MASDummyClass(object): """ diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 151c3a6edf..80c60ab3bc 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -546,27 +546,26 @@ init python in mas_windowutils: getMASWindowPos = _getMASWindowPos_Windows getMousePos = _getAbsoluteMousePos_Windows - else: - if renpy.linux: - _window_get = _getActiveWindowHandle_Linux - _tryShowNotif = _tryShowNotification_Linux - getMASWindowPos = _getMASWindowPos_Linux - getMousePos = _getAbsoluteMousePos_Linux + elif renpy.linux: + _window_get = _getActiveWindowHandle_Linux + _tryShowNotif = _tryShowNotification_Linux + getMASWindowPos = _getMASWindowPos_Linux + getMousePos = _getAbsoluteMousePos_Linux - else: - _window_get = _getActiveWindowHandle_OSX - _tryShowNotif = _tryShowNotification_OSX - - #Because we have no method of testing on Mac, we'll use the dummy function for these - getMASWindowPos = store.dummy - getMousePos = store.dummy - - #Now make sure we don't use these functions so long as we can't validate Mac - isCursorAboveMASWindow = return_false - isCursorBelowMASWindow = return_false - isCursorLeftOfMASWindow = return_false - isCursorRightOfMASWindow = return_false - isCursorInMASWindow = return_true + else: + _window_get = _getActiveWindowHandle_OSX + _tryShowNotif = _tryShowNotification_OSX + + #Because we have no method of testing on Mac, we'll use the dummy function for these + getMASWindowPos = store.dummy + getMousePos = store.dummy + + #Now make sure we don't use these functions so long as we can't validate Mac + # isCursorAboveMASWindow = return_false + # isCursorBelowMASWindow = return_false + # isCursorLeftOfMASWindow = return_false + # isCursorRightOfMASWindow = return_false + # isCursorInMASWindow = return_true init python: #List of notif quips (used for topic alerts) From e32866781d283b4642fb157027b8522a5aae7379 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:15:26 +0300 Subject: [PATCH 151/180] implement `flashMASWindow` --- Monika After Story/game/zz_windowutils.rpy | 34 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 80c60ab3bc..ae562dc824 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -266,7 +266,7 @@ init python in mas_windowutils: except Exception: return "" - def _getActiveWindowHandle_Linux(): + def _getActiveWindowHandle_Linux() -> str: """ Funtion to get the active window on Linux systems @@ -295,7 +295,7 @@ init python in mas_windowutils: except BadWindow: return "" - def _getActiveWindowHandle_OSX(): + def _getActiveWindowHandle_OSX() -> str: """ Gets the active window on macOS @@ -304,6 +304,33 @@ init python in mas_windowutils: """ return "" + def _flashMASWindow_Windows(): + """ + Tries to flash MAS window + """ + try: + hwnd = __getMASWindowHWND_Windows() + if hwnd: + winnie32api.flash_window( + hwnd, + count=None, + caption=False, + tray=True + ) + + except Exception: + pass + + def _flashMASWindow_Linux(): + """ + Tries to flash MAS window + """ + + def _flashMASWindow_OSX(): + """ + Tries to flash MAS window + """ + #Notif show internals def _tryShowNotification_Windows(title, body): """ @@ -545,16 +572,19 @@ init python in mas_windowutils: _tryShowNotif = _tryShowNotification_Windows getMASWindowPos = _getMASWindowPos_Windows getMousePos = _getAbsoluteMousePos_Windows + flashMASWindow = _flashMASWindow_Windows elif renpy.linux: _window_get = _getActiveWindowHandle_Linux _tryShowNotif = _tryShowNotification_Linux getMASWindowPos = _getMASWindowPos_Linux getMousePos = _getAbsoluteMousePos_Linux + flashMASWindow = _flashMASWindow_Linux else: _window_get = _getActiveWindowHandle_OSX _tryShowNotif = _tryShowNotification_OSX + flashMASWindow = _flashMASWindow_OSX #Because we have no method of testing on Mac, we'll use the dummy function for these getMASWindowPos = store.dummy From 7e16c56c88e562d2fdc795c3713a16e468699f0a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:25:48 +0300 Subject: [PATCH 152/180] allow `mas_display_notif` flash MAS window --- Monika After Story/game/event-handler.rpy | 4 ++-- Monika After Story/game/script-holidays.rpy | 2 +- Monika After Story/game/script-story-events.rpy | 2 +- Monika After Story/game/script-windowreacts.rpy | 2 +- Monika After Story/game/zz_consumables.rpy | 2 +- Monika After Story/game/zz_windowutils.rpy | 7 ++++++- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 19ce7cf50e..4ba9a87593 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -3032,9 +3032,9 @@ label call_next_event: ): #Create a new notif if renpy.windows: - $ mas_display_notif(m_name, mas_win_notif_quips, "Topic Alerts") + $ mas_display_notif(m_name, mas_win_notif_quips, "Topic Alerts", flash_window=True) else: - $ mas_display_notif(m_name, mas_other_notif_quips, "Topic Alerts") + $ mas_display_notif(m_name, mas_other_notif_quips, "Topic Alerts", flash_window=True) #Also check here and reset the forced idle exp if necessary if ev is not None and "keep_idle_exp" not in ev.rules: diff --git a/Monika After Story/game/script-holidays.rpy b/Monika After Story/game/script-holidays.rpy index fe045a83ee..2801bdbeaf 100644 --- a/Monika After Story/game/script-holidays.rpy +++ b/Monika After Story/game/script-holidays.rpy @@ -1232,7 +1232,7 @@ label mas_o31_lingerie: python: curr_song = songs.current_track play_song(None) - mas_display_notif("M̷̢͘ô̴͎ṇ̵͐i̴͎͂k̸̗̂ả̴̫", ["C̸̳̓ą̵́n̷̳̎ ̸̖̊y̴̦͝õ̷̯ų̷͌ ̴̼͘h̷̭̚e̴̪͝a̴̙̐ŕ̵̖ ̴̠́m̸̰̂ě̵̬?̷̮̐"], "Topic Alerts") + mas_display_notif("M̷̢͘ô̴͎ṇ̵͐i̴͎͂k̸̗̂ả̴̫", ["C̸̳̓ą̵́n̷̳̎ ̸̖̊y̴̦͝õ̷̯ų̷͌ ̴̼͘h̷̭̚e̴̪͝a̴̙̐ŕ̵̖ ̴̠́m̸̰̂ě̵̬?̷̮̐"], "Topic Alerts", flash_window=True) scene black pause 2.0 diff --git a/Monika After Story/game/script-story-events.rpy b/Monika After Story/game/script-story-events.rpy index 8e9b22a4ea..e8d7336181 100644 --- a/Monika After Story/game/script-story-events.rpy +++ b/Monika After Story/game/script-story-events.rpy @@ -1060,7 +1060,7 @@ init 5 python: label mas_random_limit_reached: #Notif so people don't get stuck here - $ mas_display_notif(m_name, ["Hey [player]..."], "Topic Alerts") + $ mas_display_notif(m_name, ["Hey [player]..."], "Topic Alerts", flash_window=True) python: limit_quips = [ diff --git a/Monika After Story/game/script-windowreacts.rpy b/Monika After Story/game/script-windowreacts.rpy index 19033adcba..8867edac1e 100644 --- a/Monika After Story/game/script-windowreacts.rpy +++ b/Monika After Story/game/script-windowreacts.rpy @@ -192,7 +192,7 @@ init 5 python: label mas_wrs_r34m: python: - mas_display_notif(m_name, ["Hey, [player]...what are you looking at?"],'Window Reactions') + mas_display_notif(m_name, ["Hey, [player]...what are you looking at?"], "Window Reactions", flash_window=True) choice = random.randint(1,10) diff --git a/Monika After Story/game/zz_consumables.rpy b/Monika After Story/game/zz_consumables.rpy index cd16f2965f..af58a5614d 100644 --- a/Monika After Story/game/zz_consumables.rpy +++ b/Monika After Story/game/zz_consumables.rpy @@ -1516,7 +1516,7 @@ label mas_consumables_generic_finish_having(consumable): and mas_getEV("mas_consumables_generic_queued_running_out").timePassedSinceLastSeen_d(datetime.timedelta(days=7)) and len(MASConsumable._getLowCons()) > 0 ): - $ mas_display_notif(m_name, ("Hey, [player]...",), "Topic Alerts") + $ mas_display_notif(m_name, ("Hey, [player]...",), "Topic Alerts", flash_window=True) $ MASEventList.queue("mas_consumables_generic_queued_running_out") #Only have one left diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index ae562dc824..440bf01761 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -645,7 +645,7 @@ init python: #TODO: Remove this alias at some point mas_getActiveWindow = mas_getActiveWindowHandle - def mas_display_notif(title, body, group=None, skip_checks=False): + def mas_display_notif(title, body, group=None, skip_checks=False, flash_window=False): """ Notification creation method @@ -656,6 +656,8 @@ init python: (Default: None) skip_checks - Whether or not we skips checks (Default: False) + flash_window - do we want to flash the MAS window (tray icon) + OUT: bool indicating status (notif shown or not (by check)) @@ -685,6 +687,9 @@ init python: renpy.substitute(title), renpy.substitute(renpy.random.choice(body)) ) + # Flash the window if needed + if flash_window: + mas_windowutils.flashMASWindow() #Play the notif sound if we have that enabled and notif was successful if persistent._mas_notification_sounds and notif_success: From 7ddcaa6df0eb388c332085a84a819725531ef2b2 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:39:08 +0300 Subject: [PATCH 153/180] fix typeerror --- .../game/python-packages/winnie32api/windows.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py index a545d26674..50b6fb8b69 100644 --- a/Monika After Story/game/python-packages/winnie32api/windows.py +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -157,8 +157,10 @@ def flash_window( flags |= FLASHW.TRAY if count is None: flags |= FLASHW.TIMERNOFG - if count == -1: + count = 0 + elif count == -1: flags |= FLASHW.TIMER + count = 0 flash_info.dwFlags = flags flash_info.uCount = count From 61117d98d873ad4c96b32e138f4d4d65319d8aa8 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:44:58 +0300 Subject: [PATCH 154/180] only flash on success --- Monika After Story/game/zz_windowutils.rpy | 29 ++++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 440bf01761..6f14d722e6 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -645,7 +645,13 @@ init python: #TODO: Remove this alias at some point mas_getActiveWindow = mas_getActiveWindowHandle - def mas_display_notif(title, body, group=None, skip_checks=False, flash_window=False): + def mas_display_notif( + title: str, + body: list[str], + group: str|None = None, + skip_checks: bool = False, + flash_window: bool = False + ) -> bool: """ Notification creation method @@ -669,11 +675,12 @@ init python: 4. And if the notification group is enabled OR if we skip checks. BUT this should only be used for introductory or testing purposes. """ - #First we want to create this location in the dict, but don't add an extra location if we're skipping checks if persistent._mas_windowreacts_notif_filters.get(group) is None and not skip_checks: persistent._mas_windowreacts_notif_filters[group] = False + notif_success = False + if ( skip_checks or ( @@ -687,17 +694,17 @@ init python: renpy.substitute(title), renpy.substitute(renpy.random.choice(body)) ) - # Flash the window if needed - if flash_window: - mas_windowutils.flashMASWindow() + if notif_success: + # Flash the window if needed + if flash_window: + mas_windowutils.flashMASWindow() - #Play the notif sound if we have that enabled and notif was successful - if persistent._mas_notification_sounds and notif_success: - renpy.sound.play("mod_assets/sounds/effects/notif.wav") + #Play the notif sound if we have that enabled and notif was successful + if persistent._mas_notification_sounds: + renpy.sound.play("mod_assets/sounds/effects/notif.wav") - #Now we return true if notif was successful, false otherwise - return notif_success - return False + #Now we return true if notif was successful, false otherwise + return notif_success def mas_isFocused(): """ From 76b10dcdb946a15c9fd4971fbf12cec3d0edec1f Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 6 Sep 2022 07:05:04 +0300 Subject: [PATCH 155/180] update `winnie32api` yes, again --- .../python-packages/winnie32api/__init__.py | 22 +- .../python-packages/winnie32api/common.py | 15 - .../python-packages/winnie32api/errors.py | 27 + .../game/python-packages/winnie32api/mouse.py | 9 +- .../python-packages/winnie32api/notifs.py | 568 +++++++++++++----- .../python-packages/winnie32api/windows.py | 34 +- 6 files changed, 480 insertions(+), 195 deletions(-) create mode 100644 Monika After Story/game/python-packages/winnie32api/errors.py diff --git a/Monika After Story/game/python-packages/winnie32api/__init__.py b/Monika After Story/game/python-packages/winnie32api/__init__.py index 97ffb1dcbc..02ebf1ecd3 100644 --- a/Monika After Story/game/python-packages/winnie32api/__init__.py +++ b/Monika After Story/game/python-packages/winnie32api/__init__.py @@ -8,22 +8,8 @@ __title__ = "winnie32api" __author__ = "Booplicate" -__version__ = "0.0.4" +__version__ = "0.1.0" -import ctypes - -from .mouse import ( - get_screen_mouse_pos -) -from .windows import ( - get_hwnd_by_title, - get_window_title, - get_window_rect, - flash_window, - unflash_window, - get_active_window_hwnd, - get_active_window_title, - get_active_window_rect -) - -from .notifs import WindowsNotifManager, WindowsNotif +from .mouse import * +from .windows import * +from .notifs import * diff --git a/Monika After Story/game/python-packages/winnie32api/common.py b/Monika After Story/game/python-packages/winnie32api/common.py index 817699743d..9df75297bf 100644 --- a/Monika After Story/game/python-packages/winnie32api/common.py +++ b/Monika After Story/game/python-packages/winnie32api/common.py @@ -16,21 +16,6 @@ kernel32 = ctypes.windll.kernel32 -HWND = int - -class Winnie32APIError(Exception): pass -class WinAPIError(Winnie32APIError): - """ - Represents an error in win API - """ - def __init__(self, msg: str, code: int): - self.msg = msg - self.code = code - - def __str__(self) -> str: - return f"{self.msg}. Status code: {self.code}" - - Coord = int class Point(NamedTuple): diff --git a/Monika After Story/game/python-packages/winnie32api/errors.py b/Monika After Story/game/python-packages/winnie32api/errors.py new file mode 100644 index 0000000000..48fed9f17d --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/errors.py @@ -0,0 +1,27 @@ +class Winnie32APIError(Exception): + """ + The base class for all exceptions in winnie32api + """ + +class WinAPIError(Winnie32APIError): + """ + Represents an error in win API + """ + def __init__(self, msg: str, code: int):# pylint: disable=super-init-not-called + self.msg = msg + self.code = code + + def __str__(self) -> str: + return f"{self.msg}. Status code: {self.code}" + +class NotifError(Winnie32APIError): + """ + The base class for notification-related exceptions + """ + +class ManagerAlreadyExistsError(NotifError): + """ + An error raised when tried to create more than one manager + """ + def __str__(self) -> str: + return "notification manager has already been defined for this app" diff --git a/Monika After Story/game/python-packages/winnie32api/mouse.py b/Monika After Story/game/python-packages/winnie32api/mouse.py index 44cfb5b496..eab4b835d3 100644 --- a/Monika After Story/game/python-packages/winnie32api/mouse.py +++ b/Monika After Story/game/python-packages/winnie32api/mouse.py @@ -1,7 +1,12 @@ +__all__ = ( + "get_screen_mouse_pos", +) + import ctypes import ctypes.wintypes as wt -from .common import Point, WinAPIError, _get_last_err +from .common import Point, _get_last_err +from .errors import WinAPIError user32 = ctypes.windll.user32 @@ -20,4 +25,4 @@ def get_screen_mouse_pos() -> Point: if not result: raise WinAPIError("failed to get mouse position", _get_last_err()) - return Point(c_point.x, c_point.y) + return Point(c_point.x, c_point.y)# type: ignore diff --git a/Monika After Story/game/python-packages/winnie32api/notifs.py b/Monika After Story/game/python-packages/winnie32api/notifs.py index 04a71266b4..37a767e096 100644 --- a/Monika After Story/game/python-packages/winnie32api/notifs.py +++ b/Monika After Story/game/python-packages/winnie32api/notifs.py @@ -1,10 +1,20 @@ +# pylint: disable=attribute-defined-outside-init +# pylint: disable=invalid-name +from __future__ import annotations + +__all__ = ( + "NotifManager", +) + import ctypes import ctypes.wintypes as wt import weakref -from collections import deque -from typing import Optional +import threading +import atexit +from collections.abc import Callable -from .common import WinAPIError, Winnie32APIError, _get_last_err +from .common import _get_last_err +from .errors import WinAPIError, ManagerAlreadyExistsError user32 = ctypes.windll.user32 @@ -12,16 +22,23 @@ shell32 = ctypes.windll.shell32 -LRESULT = wt.LPARAM#ctypes.c_long -WNDPROC = ctypes.WINFUNCTYPE(LRESULT, wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) +APP_ID = 922 + +# This is missing from wintypes +LRESULT = wt.LPARAM#ctypes.c_long +# This has undocumented value, but seems to work CW_USEDEFAULT = -2147483648 -WM_USER = 0x0400 +# There's literally only one place on the internet where it says the value is -3 +# and it's not microsoft docs. At least it seems to work... HWND_MESSAGE = -3 -APP_ID = 922 +# Undocumented, but probably is correct +NOTIFYICON_VERSION_4 = 4 +# The base value of user-defined msgs +WM_USER = 0x0400 -NOTIFS_LIMIT = 100 +WNDPROC = ctypes.WINFUNCTYPE(LRESULT, wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) class NotifyIconDataW(ctypes.Structure): """ @@ -64,6 +81,20 @@ class WndClassExw(ctypes.Structure): ("hIconSm", wt.HICON), ] +class Msg(ctypes.Structure): + """ + Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg + """ + _fields_ = [ + ("hwnd", wt.HWND), + ("message", wt.UINT), + ("wParam", wt.WPARAM), + ("lParam", wt.LPARAM), + ("time", wt.DWORD), + ("pt", wt.POINT), + ("lPrivate", wt.DWORD) + ] + class NIF(): """ @@ -227,9 +258,68 @@ class IMAGE(): 2. Copies a cursor. 1. Copies an icon. """ - BITMAP = wt.UINT(0) - CURSOR = wt.UINT(2) - ICON = wt.UINT(1) + BITMAP = 0 + CURSOR = 2 + ICON = 1 + +# class WM(): +# """ +# The documentation is to scattered for these +# constants, so I implemented only the minimal set +# Docs: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-notifications +# """ +# CREATE = 0x0001 +# DESTROY = 0x0002 +# GETMINMAXINFO = 0x0024 +# NCCALCSIZE = 0x0083 +# NCCREATE = 0x0081 +# NCDESTROY = 0x0082 +# CLOSE = 0x0010 +# QUIT = 0x0012 + +# class PM(): +# """ +# NOREMOVE. Messages are not removed from the queue +# after processing by PeekMessage +# REMOVE. Messages are removed from the queue after processing +# NOYIELD. Prevents the system from releasing any thread that is waiting +# for the caller to go idle (see WaitForInputIdle). +# Combine this value with either PM_NOREMOVE or PM_REMOVE +# """ +# NOREMOVE = 0x0000 +# REMOVE = 0x0001 +# NOYIELD = 0x0002 + +class MsgValue(): + """ + A namespace for msg values constants + """ + TRAY_ICON_EVENT = WM_USER + 1 + SHUTDOWN_THREAD = WM_USER + 999 + +class LParamValue(): + """ + A namespace for LPARAM values constants + these are only some I was able to get via try and fail + sadly they have no documented values + """ + NOTIF_SHOW = 60425218 + NOTIF_HIDE = 60425220 + # Not sure about this one + NOTIF_DISMISS = 60425221 + HOVER = 60424704 + LMB_PRESS = 60424705 + LMB_DPRESS = 60424707# double press + # Not sure about this one + LMB_HOLD = 60425216 + LMB_RELEASE = 60424706 + MMB_PRESS = 60424711 + MMB_RELEASE = 60424712 + RMB_PRESS = 60424708 + RMB_DPRESS = 60424710# double press + # Not sure about this one + RMB_HOLD = 60424315 + RMB_RELEASE = 60424709 user32.LoadImageW.argtypes = ( @@ -270,128 +360,113 @@ class IMAGE(): shell32.Shell_NotifyIconW.argtypes = (wt.DWORD, ctypes.POINTER(NotifyIconDataW)) shell32.Shell_NotifyIconW.restype = wt.BOOL +user32.GetMessageW.argtypes = (ctypes.POINTER(Msg), wt.HWND, wt.UINT, wt.UINT) +user32.GetMessageW.restype = wt.INT -class MaxNotifsReachedError(Winnie32APIError): - """ - An error raised when spawned too many WindowsNotif - """ - def __str__(self) -> str: - return "too many notification" +# user32.PeekMessageW.argtypes = (ctypes.POINTER(Msg), wt.HWND, wt.UINT, wt.UINT, wt.UINT) +# user32.PeekMessageW.restype = wt.BOOL -class InvalidNotifAccessError(Winnie32APIError): - """ - An error raised when tried to use a cleared WindowsNotif - """ - def __str__(self) -> str: - return "can't use notification after it's been cleared" +user32.TranslateMessage.argtypes = (ctypes.POINTER(Msg),) +user32.TranslateMessage.restype = wt.BOOL + +user32.DispatchMessageW.argtypes = (ctypes.POINTER(Msg),) +user32.DispatchMessageW.restype = LRESULT + +user32.PostMessageW.argtypes = (wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) +user32.PostMessageW.restype = wt.BOOL -class WindowsNotif(): + +NotifCallback = Callable[[], None] + +class _App(): """ - Class reprensets a windows notification + Private class to represent an app """ - _NOTIF_ID_POOL = deque( - map(str, range(NOTIFS_LIMIT)), - maxlen=NOTIFS_LIMIT - ) - def __init__( self, - app_name: str, - icon_path: Optional[str], - title: str, - body: str + name: str, + icon_path: str|None, + on_show: NotifCallback|None, + on_hide: NotifCallback|None, + on_dismiss: NotifCallback|None, + on_hover: NotifCallback|None, + on_lmb_click: NotifCallback|None, + on_lmb_dclick: NotifCallback|None, + on_mmb_click: NotifCallback|None, + on_rmb_click: NotifCallback|None, + on_rmb_dclick: NotifCallback|None ): """ - Constructs a new windows notification + Constructor IN: - app_name - the name of the app + name - the name of the app icon_path - path to optional icon for this notif - title - the notif title - body - the notif body - """ - # Predefine in case we crash + on_hover - on hover event callback + on_lmb_click - on left click event callback + on_lmb_dclick - on left double click event callback + on_mmb_click - on middle click event callback + on_rmb_click - on right click event callback + on_rmb_dclick - on right double click event callback + """ + self._name = name + self._icon_path = icon_path + + self._callback_map = { + LParamValue.NOTIF_SHOW: on_show, + LParamValue.NOTIF_HIDE: on_hide, + LParamValue.NOTIF_DISMISS: on_dismiss, + LParamValue.HOVER: on_hover, + LParamValue.LMB_PRESS: on_lmb_click, + LParamValue.LMB_DPRESS: on_lmb_dclick, + LParamValue.MMB_PRESS: on_mmb_click, + LParamValue.RMB_PRESS: on_rmb_click, + LParamValue.RMB_DPRESS: on_rmb_dclick + } + + self._thread: threading.Thread | None = None + self._is_shown = False + self._hinstance = None self._win_cls = None self._cls_atom = None self._hicon = None self._hwnd = None - self._nid = None - self._notif_id = None - - if not self._NOTIF_ID_POOL: - raise MaxNotifsReachedError() - self._app_name = app_name - self._icon_path = icon_path - self._title = title - self._body = body - - self._used = False - self._notif_id = self._NOTIF_ID_POOL.popleft() - - self._after_init() - - def _after_init(self): + def _init(self): + """ + Allocated resources for the app + """ self._set_hinstance() self._register_win_cls() self._load_icon() self._create_win() + self._show_tray_icon() def _deinit(self): - self._hide_notif() + """ + Deallocated the app resources + """ + self._hide_tray_icon() self._destroy_win() self._unload_icon() self._unregister_win_cls() + self._unset_hinstance() def __del__(self): """ Cleanup on gc """ - if self._notif_id is not None: - self._deinit() - self._NOTIF_ID_POOL.append(self._notif_id) - self._notif_id = None - - def __call__(self): - """ - Shortcut for the send method - """ - if self._notif_id is None: - raise InvalidNotifAccessError() - if not self._used: - self._used = True - self._display_notif() - - def send(self): - """ - Sends this notif, once - """ - self() - - def reset(self): - """ - Resets this notif allowing it to be send again - """ - if self._notif_id is None: - raise InvalidNotifAccessError() + self.stop() + # Just in case self._deinit() - self._after_init() - self._used = False - - def clear(self): - """ - Clears this notif freeing the resources - The notif can't be used anymore after it's been cleared - """ - self.__del__() def _load_icon(self): """ - Loads the notification icon + Loads the app icon """ if self._icon_path: - icon_flags = LR.LOADFROMFILE | LR.DEFAULTSIZE + icon_flags = LR.DEFAULTCOLOR | LR.LOADFROMFILE | LR.DEFAULTSIZE | LR.SHARED hicon = user32.LoadImageW( None,# Use NULL since we're loading a "stand-alone" resource self._icon_path, @@ -400,32 +475,46 @@ def _load_icon(self): 0, icon_flags ) + if not hicon: + raise WinAPIError("failed to load icon", _get_last_err()) else: - hicon = 0 + hicon = 0# TODO: doesn't work self._hicon = hicon def _unload_icon(self): """ - Unloads the notification icon + Unloads the app icon """ if self._hicon: user32.DestroyIcon(self._hicon) + self._hicon = None def _set_hinstance(self): """ Gets the handler of this dll """ - self._hinstance = handle = kernel32.GetModuleHandleW(None) + handle = kernel32.GetModuleHandleW(None) if not handle: raise WinAPIError("failed to get module handle", _get_last_err()) + self._hinstance = handle + + def _unset_hinstance(self): + """ + Removes the pointer to the handler of this dll + """ + self._hinstance = None def _register_win_cls(self): """ Registers a window class """ def winproc(hwnd: wt.HWND, msg: wt.UINT, wparam: wt.WPARAM, lparam: wt.LPARAM) -> LRESULT: + cb = self._callback_map.get(lparam, None)# type: ignore + if cb: + cb() + # print(f"{hex(msg)}: {wparam} | {lparam}")# type: ignore return user32.DefWindowProcW(hwnd, msg, wparam, lparam) self._win_cls = win_cls = WndClassExw() @@ -438,36 +527,39 @@ def winproc(hwnd: wt.HWND, msg: wt.UINT, wparam: wt.WPARAM, lparam: wt.LPARAM) - win_cls.hIcon = 0 win_cls.hCursor = 0 win_cls.hbrBackground = 0 - win_cls.lpszClassName = self._app_name + self._notif_id + win_cls.lpszClassName = self._name - self._cls_atom = cls_atom = user32.RegisterClassExW(ctypes.byref(win_cls)) + cls_atom = user32.RegisterClassExW(ctypes.byref(win_cls)) if not cls_atom: raise WinAPIError("failed to create class ATOM", _get_last_err()) + self._cls_atom = cls_atom def _unregister_win_cls(self): """ - Unregisters a window class + Unregisters the window class """ if self._win_cls: user32.UnregisterClassW(self._win_cls.lpszClassName, self._hinstance) + self._win_cls = None + self._cls_atom = None def _create_win(self): """ - Creates a notification window + Creates a tray window """ win_style = WS.OVERLAPPED | WS.SYSMENU hwnd = user32.CreateWindowExW( 0, self._cls_atom, # self._win_cls.lpszClassName, - # self._app_name, + # self._name, self._win_cls.lpszClassName, win_style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - None, + HWND_MESSAGE, None, self._hinstance, None @@ -479,88 +571,268 @@ def _create_win(self): def _destroy_win(self): """ - Destroys the notification window + Destroys the tray window """ if self._hwnd: user32.DestroyWindow(self._hwnd) + self._hwnd = None - def _display_notif(self): + def _get_base_nid(self) -> NotifyIconDataW: """ - Displays this notification + Constructs and returns "base" version of NotifyIconDataW """ - self._nid = nid = NotifyIconDataW() + nid = NotifyIconDataW() nid.cbSize = ctypes.sizeof(nid) nid.hWnd = self._hwnd nid.uID = APP_ID - nid.uFlags = ( - NIF.ICON | NIF.INFO | NIF.STATE | NIF.MESSAGE | NIF.TIP | NIF.SHOWTIP - ) - nid.uCallbackMessage = WM_USER + 2 + nid.uFlags = NIF.ICON | NIF.STATE | NIF.MESSAGE | NIF.TIP + nid.uCallbackMessage = MsgValue.TRAY_ICON_EVENT nid.hIcon = self._hicon - nid.szTip = self._app_name[:128] - nid.szInfo = self._body[:256] - nid.uVersion = 4 - nid.szInfoTitle = self._title[:64] + nid.szTip = self._name[:128] + # nid.szInfo = body[:256] + nid.uVersion = NOTIFYICON_VERSION_4 + # nid.szInfoTitle = title[:64] nid.dwInfoFlags = NIIF.NOSOUND | NIIF.USER | NIIF.LARGE_ICON | NIIF.RESPECT_QUIET_TIME - shell32.Shell_NotifyIconW(NIM.ADD, ctypes.byref(nid)) - shell32.Shell_NotifyIconW(NIM.SETVERSION, ctypes.byref(nid)) + return nid + + def _run_event_loop(self): + msg = Msg() + msg_p = ctypes.byref(msg) + hwnd = self._hwnd + # both 0s == no filter + filter_min = 0 + filter_max = 0 + # print("starting") + while (rv := user32.GetMessageW(msg_p, hwnd, filter_min, filter_max)) != 0: + # print("pumped") + if rv == -1: + raise WinAPIError("GetMessageW returned an error code", _get_last_err()) + if msg.message == MsgValue.SHUTDOWN_THREAD: + # print("shutting down") + break + user32.TranslateMessage(msg_p) + user32.DispatchMessageW(msg_p) + + # print("exiting") + + def _run(self): + """ + Shows the app + runs the event loop + hides the app + NOTE: Blocking call + """ + self._init() + try: + self._run_event_loop() + finally: + self._deinit() + + def start(self): + """ + Runs the app + """ + if not self._thread: + self._thread = thread = threading.Thread(target=self._run, daemon=True) + thread.start() + + def stop(self): + """ + Stops the app + NOTE: this will block until the app is stopped + """ + if self._hwnd: + user32.PostMessageW(self._hwnd, MsgValue.SHUTDOWN_THREAD, 0, 0) + if self._thread: + self._thread.join() + self._thread = None + + def _show_tray_icon(self) -> bool: + """ + Shows tha app tray icon + """ + rv = False + + if not self._is_shown: + nid = self._get_base_nid() + rv = bool(shell32.Shell_NotifyIconW(NIM.ADD, ctypes.byref(nid))) + if rv: + shell32.Shell_NotifyIconW(NIM.SETVERSION, ctypes.byref(nid)) + self._is_shown = True + + return rv + + def _hide_tray_icon(self) -> bool: + """ + Hides tha app tray icon + """ + rv = False + + if self._is_shown: + nid = self._get_base_nid() + rv = bool(shell32.Shell_NotifyIconW(NIM.DELETE, ctypes.byref(nid))) + + self._is_shown = False + + return rv + + def send_notif(self, title: str, body: str) -> bool: + """ + Sends a notification + + IN: + title - the title of the notification + body - the body of the notification + """ + if not self._is_shown: + return False + + nid = self._get_base_nid() + + nid.uFlags |= NIF.INFO | NIF.SHOWTIP + nid.szInfo = body[:256] + nid.szInfoTitle = title[:64] - def _hide_notif(self): + return bool(shell32.Shell_NotifyIconW(NIM.MODIFY, ctypes.byref(nid))) + + def clear_notifs(self): """ - Hides this notification + Clears notifications """ - if self._nid: - shell32.Shell_NotifyIconW(NIM.DELETE, ctypes.byref(self._nid)) - user32.UpdateWindow(self._hwnd) + if not self._is_shown: + return + + # According to microsoft docs this should work + # but it works 1 out of 20 times... + # nid = self._get_base_nid() + # nid.uFlags = NIF.INFO + # nid.szInfo = "" + # nid.szInfoTitle = "" + # shell32.Shell_NotifyIconW(NIM.MODIFY, ctypes.byref(nid)) + + # So we're doing this hack + self._hide_tray_icon() + self._show_tray_icon() + -class WindowsNotifManager(): +class NotifManager(): """ Notification manager """ - def __init__(self, app_name: str, icon_path: Optional[str]): + _instance = None + + def __new__(cls, *args, **kwargs) -> NotifManager:# pylint: disable=unused-argument + """ + Singleton implementation + """ + if cls._instance is not None: + raise ManagerAlreadyExistsError() + + self = super().__new__(cls) + cls._instance = weakref.ref(self) + + return self + + def __init__( + self, + app_name: str, + icon_path: str|None = None, + on_show: NotifCallback|None = None, + on_hide: NotifCallback|None = None, + on_dismiss: NotifCallback|None = None, + on_hover: NotifCallback|None = None, + on_lmb_click: NotifCallback|None = None, + on_lmb_dclick: NotifCallback|None = None, + on_mmb_click: NotifCallback|None = None, + on_rmb_click: NotifCallback|None = None, + on_rmb_dclick: NotifCallback|None = None + ): """ Constructor IN: app_name - the app name shared by the notifs icon_path - the path to the icon shared by the notifs - """ - self._app_name = app_name - self._icon_path = icon_path + on_show - on notif show event callback + (Default: None) + on_hide - on notif hide event callback + (Default: None) + on_dismiss - on notif dismiss event callback + if a dismiss event has been fired, hide won't be fired + (Default: None) + on_hover - on hover event callback + NOTE: hover callback may run event during click events + (Default: None) + on_lmb_click - on left click event callback + (Default: None) + on_lmb_dclick - on left double click event callback + NOTE: before a double click event, a click event will still be fired + (Default: None) + on_mmb_click - on middle click event callback + (Default: None) + on_rmb_click - on right click event callback + (Default: None) + on_rmb_dclick - on right double click event callback + NOTE: before a double click event, a click event will still be fired + (Default: None) + """ + # Ask the interpreter for cleanup + atexit.register(self.shutdown) + + self._app: _App|None = _App( + app_name, + icon_path, + on_show=on_show, + on_hide=on_hide, + on_dismiss=on_dismiss, + on_hover=on_hover, + on_lmb_click=on_lmb_click, + on_lmb_dclick=on_lmb_dclick, + on_mmb_click=on_mmb_click, + on_rmb_click=on_rmb_click, + on_rmb_dclick=on_rmb_dclick + ) + self._app.start() - self._notifs: deque[weakref.ReferenceType[WindowsNotif]] = deque(maxlen=NOTIFS_LIMIT) + def __del__(self): + self.shutdown() - def spawn(self, title: str, body: str) -> WindowsNotif: + def is_ready(self) -> bool: """ - Spawns a notif, but doesn't send it - - IN: - title - the title of the notification - body - the body of the notification + Checks if the manager and app are ready to send notifications """ - notif = WindowsNotif(self._app_name, self._icon_path, title, body) - self._notifs.append(weakref.ref(notif)) - return notif + return self._app is not None and self._app._is_shown# pylint: disable=protected-access - def send(self, title: str, body: str) -> WindowsNotif: + def send(self, title: str, body: str) -> bool: """ - Spawns and sends a notif + Sends a notifification IN: title - the title of the notification body - the body of the notification + + OUT: + boolean - success status """ - notif = self.spawn(title, body) - notif.send() - return notif + if not self._app: + return False + return self._app.send_notif(title, body) def clear(self): """ Clears all notification this manager has access to + To completely free the resources on quit, use + the 'shutdown' method + """ + if self._app: + self._app.clear_notifs() + + def shutdown(self): + """ + A method to call on shutdown of your app + Gracefully clears notifs, hides the icon, frees the resources """ - for notif_ref in self._notifs: - notif = notif_ref() - if notif: - notif.clear() - self._notifs.clear() + if self._app: + self._app.stop() + # Incase you're a clever laf and run the cleanup, we can abort it + atexit.unregister(self.shutdown) + self._app = None diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py index 50b6fb8b69..3b5618b19b 100644 --- a/Monika After Story/game/python-packages/winnie32api/windows.py +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -1,18 +1,28 @@ +# pylint: disable=attribute-defined-outside-init +# pylint: disable=invalid-name +from __future__ import annotations + +__all__ = ( + "get_hwnd_by_title", + "get_window_title", + "get_window_rect", + "flash_window", + "unflash_window", + "get_active_window_hwnd", + "get_active_window_title", + "get_active_window_rect" +) + import ctypes import ctypes.wintypes as wt -from typing import ( - Optional -) - from .common import ( - # HWND, Rect, Pack, - WinAPIError, _get_last_err, _reset_last_err ) +from .errors import WinAPIError user32 = ctypes.windll.user32 @@ -70,7 +80,7 @@ class FLASHW(): user32.GetForegroundWindow.restype = wt.HWND -def get_hwnd_by_title(title: str) -> Optional[int]: +def get_hwnd_by_title(title: str) -> int|None: """ Returns first window hwnd with the given title """ @@ -124,11 +134,11 @@ def get_window_rect(hwnd: int) -> Rect: if not result: raise WinAPIError("failed to get window rect", _get_last_err()) - return Rect.from_coords(c_rect.left, c_rect.top, c_rect.right, c_rect.bottom) + return Rect.from_coords(c_rect.left, c_rect.top, c_rect.right, c_rect.bottom)# type: ignore def flash_window( hwnd: int, - count: Optional[int] = 1, + count: int|None = 1, caption: bool = True, tray: bool = True ): @@ -181,7 +191,7 @@ def unflash_window(hwnd: int): user32.FlashWindowEx(ctypes.byref(flash_info)) -def get_active_window_hwnd() -> Optional[int]: +def get_active_window_hwnd() -> int|None: """ Returns active window title hwnd (id) """ @@ -191,7 +201,7 @@ def get_active_window_hwnd() -> Optional[int]: return active_win_hwnd -def get_active_window_title() -> Optional[str]: +def get_active_window_title() -> str|None: """ Returns active window title as a str """ @@ -201,7 +211,7 @@ def get_active_window_title() -> Optional[str]: return get_window_title(hwnd) -def get_active_window_rect() -> Optional[Rect]: +def get_active_window_rect() -> Rect|None: """ Returns active window rect """ From 0005303e40fa932a0918ae970fd36429667f5d1c Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 6 Sep 2022 07:38:52 +0300 Subject: [PATCH 156/180] smol update for win utils --- Monika After Story/game/zz_windowutils.rpy | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 6f14d722e6..53331b8291 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -48,8 +48,6 @@ init python in mas_windowutils: ## Windows # The notification manager WIN_NOTIF_MANAGER = None - # All current notifs, - win_notif_list = [] #We can only do this on windows if renpy.windows: @@ -62,7 +60,7 @@ init python in mas_windowutils: import winnie32api #Now we initialize the notification class - WIN_NOTIF_MANAGER = winnie32api.WindowsNotifManager( + WIN_NOTIF_MANAGER = winnie32api.NotifManager( renpy.config.name, os.path.join(renpy.config.gamedir, "mod_assets/mas_icon.ico") ) @@ -345,10 +343,7 @@ init python in mas_windowutils: bool. True if the notification was successfully sent, False otherwise """ try: - notif = WIN_NOTIF_MANAGER.send(title, body) - win_notif_list.append(notif) - return True - + return WIN_NOTIF_MANAGER.send(title, body) except Exception: return False @@ -742,7 +737,6 @@ init python: """ if renpy.windows and store.mas_windowreacts.can_show_notifs: mas_windowutils.WIN_NOTIF_MANAGER.clear() - mas_windowutils.win_notif_list.clear() def mas_checkForWindowReacts(): """ From 9b2909c54a38c7cdf159cc341ed18595a0d520bb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 6 Sep 2022 22:20:25 +0300 Subject: [PATCH 157/180] more wr improvements implemented notify events optimised HWND getting --- .../python-packages/winnie32api/__init__.py | 2 +- .../python-packages/winnie32api/notifs.py | 19 +++- .../python-packages/winnie32api/windows.py | 22 ++++- Monika After Story/game/zz_windowutils.rpy | 93 +++++++++++++------ 4 files changed, 104 insertions(+), 32 deletions(-) diff --git a/Monika After Story/game/python-packages/winnie32api/__init__.py b/Monika After Story/game/python-packages/winnie32api/__init__.py index 02ebf1ecd3..73c74a7124 100644 --- a/Monika After Story/game/python-packages/winnie32api/__init__.py +++ b/Monika After Story/game/python-packages/winnie32api/__init__.py @@ -8,7 +8,7 @@ __title__ = "winnie32api" __author__ = "Booplicate" -__version__ = "0.1.0" +__version__ = "0.1.2" from .mouse import * from .windows import * diff --git a/Monika After Story/game/python-packages/winnie32api/notifs.py b/Monika After Story/game/python-packages/winnie32api/notifs.py index 37a767e096..e211c0d614 100644 --- a/Monika After Story/game/python-packages/winnie32api/notifs.py +++ b/Monika After Story/game/python-packages/winnie32api/notifs.py @@ -372,6 +372,12 @@ class LParamValue(): user32.DispatchMessageW.argtypes = (ctypes.POINTER(Msg),) user32.DispatchMessageW.restype = LRESULT +kernel32.GetCurrentThreadId.argtypes = () +kernel32.GetCurrentThreadId.restype = wt.DWORD + +user32.AttachThreadInput.argtypes = (wt.DWORD, wt.DWORD, wt.BOOL) +user32.AttachThreadInput.restype = wt.BOOL + user32.PostMessageW.argtypes = (wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) user32.PostMessageW.restype = wt.BOOL @@ -514,6 +520,7 @@ def winproc(hwnd: wt.HWND, msg: wt.UINT, wparam: wt.WPARAM, lparam: wt.LPARAM) - cb = self._callback_map.get(lparam, None)# type: ignore if cb: cb() + return 1# type: ignore # print(f"{hex(msg)}: {wparam} | {lparam}")# type: ignore return user32.DefWindowProcW(hwnd, msg, wparam, lparam) @@ -616,23 +623,31 @@ def _run_event_loop(self): # print("exiting") - def _run(self): + def _run(self, main_th_id: int): """ Shows the app + runs the event loop + hides the app NOTE: Blocking call """ + child_th_id = kernel32.GetCurrentThreadId() + user32.AttachThreadInput(main_th_id, child_th_id, True) self._init() try: self._run_event_loop() finally: self._deinit() + user32.AttachThreadInput(main_th_id, child_th_id, False) def start(self): """ Runs the app """ if not self._thread: - self._thread = thread = threading.Thread(target=self._run, daemon=True) + main_th_id = kernel32.GetCurrentThreadId() + self._thread = thread = threading.Thread( + target=self._run, + args=(main_th_id,), + daemon=True + ) thread.start() def stop(self): diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py index 3b5618b19b..93b7d8d490 100644 --- a/Monika After Story/game/python-packages/winnie32api/windows.py +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -8,6 +8,7 @@ "get_window_rect", "flash_window", "unflash_window", + "set_active_window", "get_active_window_hwnd", "get_active_window_title", "get_active_window_rect" @@ -136,6 +137,7 @@ def get_window_rect(hwnd: int) -> Rect: return Rect.from_coords(c_rect.left, c_rect.top, c_rect.right, c_rect.bottom)# type: ignore + def flash_window( hwnd: int, count: int|None = 1, @@ -152,9 +154,6 @@ def flash_window( None means flash infinitely until the window becomes focused caption - do we flash window caption tray - do weflash tray icon - - OUT: - bool - success status """ flash_info = FlashWInfo() flash_info.cbSize = ctypes.sizeof(flash_info) @@ -191,6 +190,23 @@ def unflash_window(hwnd: int): user32.FlashWindowEx(ctypes.byref(flash_info)) +def set_active_window(hwnd: int): + """ + Sets focus to a new window + NOTE: + A process may or may not allow to change "foreground" window + to other processes + It's impossible to "activate" a window from another process + """ + # Also tried: + # - linking threads, no result + # - emulating input, no result + user32.SetFocus(hwnd)# err 5 + user32.BringWindowToTop(hwnd)# no err, but no result + user32.SetForegroundWindow(hwnd)# err 1400 + # user32.SetActiveWindow(hwnd)# err 1400 + + def get_active_window_hwnd() -> int|None: """ Returns active window title hwnd (id) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 53331b8291..09c5314494 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -48,6 +48,8 @@ init python in mas_windowutils: ## Windows # The notification manager WIN_NOTIF_MANAGER = None + # Window handler + HWND = None #We can only do this on windows if renpy.windows: @@ -62,7 +64,21 @@ init python in mas_windowutils: #Now we initialize the notification class WIN_NOTIF_MANAGER = winnie32api.NotifManager( renpy.config.name, - os.path.join(renpy.config.gamedir, "mod_assets/mas_icon.ico") + os.path.join(renpy.config.gamedir, "mod_assets/mas_icon.ico"), + on_dismiss=lambda: ( + focusMASWindow(), + _unflashMASWindow_Windows(), + WIN_NOTIF_MANAGER.clear() + ), + on_lmb_click=lambda: ( + focusMASWindow(), + _unflashMASWindow_Windows(), + WIN_NOTIF_MANAGER.clear() + ), + on_rmb_click=lambda: ( + _unflashMASWindow_Windows(), + WIN_NOTIF_MANAGER.clear() + ) ) except Exception: @@ -171,27 +187,27 @@ init python in mas_windowutils: except BadWindow: return None - def __getMASWindowHWND_Windows(): + def __getMASWindowHWND_Windows() -> int|None: """ Gets the hWnd of the MAS window - NOTE: Windows ONLY - OUT: int - represents the hWnd of the MAS window + None - if we failed to get hwnd """ - hwnd = None - #Verify we can actually do this before doing anything - if not store.mas_windowreacts.can_do_windowreacts: - return hwnd + global HWND - try: - # TODO: consider caching this - hwnd = winnie32api.get_hwnd_by_title(store.mas_getWindowTitle()) - except Exception: - pass + #Verify we can actually do this before doing anything + if store.mas_windowreacts.can_do_windowreacts: + if HWND is None: + try: + HWND = winnie32api.get_hwnd_by_title(store.mas_getWindowTitle()) + except Exception: + HWND = None + else: + HWND = None - return hwnd + return HWND def __getAbsoluteGeometry_Linux(win): """ @@ -306,18 +322,22 @@ init python in mas_windowutils: """ Tries to flash MAS window """ - try: - hwnd = __getMASWindowHWND_Windows() - if hwnd: - winnie32api.flash_window( - hwnd, - count=None, - caption=False, - tray=True - ) + hwnd = __getMASWindowHWND_Windows() + if hwnd: + winnie32api.flash_window( + hwnd, + count=None, + caption=False, + tray=True + ) - except Exception: - pass + def _unflashMASWindow_Windows(): + """ + Tries to stop flashing MAS window + """ + hwnd = __getMASWindowHWND_Windows() + if hwnd: + winnie32api.unflash_window(hwnd) def _flashMASWindow_Linux(): """ @@ -329,6 +349,24 @@ init python in mas_windowutils: Tries to flash MAS window """ + def _focusMASWindow_Windows(): + """ + Tries to set focus on MAS window + """ + hwnd = __getMASWindowHWND_Windows() + if hwnd: + winnie32api.set_active_window(hwnd) + + def _focusMASWindow_Linux(): + """ + Tries to set focus on MAS window + """ + + def _focusMASWindow_OSX(): + """ + Tries to set focus on MAS window + """ + #Notif show internals def _tryShowNotification_Windows(title, body): """ @@ -568,6 +606,7 @@ init python in mas_windowutils: getMASWindowPos = _getMASWindowPos_Windows getMousePos = _getAbsoluteMousePos_Windows flashMASWindow = _flashMASWindow_Windows + focusMASWindow = _focusMASWindow_Windows elif renpy.linux: _window_get = _getActiveWindowHandle_Linux @@ -575,11 +614,13 @@ init python in mas_windowutils: getMASWindowPos = _getMASWindowPos_Linux getMousePos = _getAbsoluteMousePos_Linux flashMASWindow = _flashMASWindow_Linux + focusMASWindow = _focusMASWindow_Linux else: _window_get = _getActiveWindowHandle_OSX _tryShowNotif = _tryShowNotification_OSX flashMASWindow = _flashMASWindow_OSX + focusMASWindow = _focusMASWindow_OSX #Because we have no method of testing on Mac, we'll use the dummy function for these getMASWindowPos = store.dummy @@ -735,7 +776,7 @@ init python: """ Clears all tray icons (also action center on win10) """ - if renpy.windows and store.mas_windowreacts.can_show_notifs: + if renpy.windows: mas_windowutils.WIN_NOTIF_MANAGER.clear() def mas_checkForWindowReacts(): From fc4af745b6e7cec91788d69177240c61b258892d Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 7 Sep 2022 00:23:56 +0300 Subject: [PATCH 158/180] remove `mas_with_statement` --- Monika After Story/game/definitions.rpy | 49 ------------------------- 1 file changed, 49 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 42bcb87479..1710554861 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -186,55 +186,6 @@ python early: # allows us to use a more advanced string formatting renpy.substitutions.formatter = MASFormatter() - def mas_with_statement(trans, always=False, paired=None, clear=True): - """ - Causes a transition to occur. This is the Python equivalent of the - with statement - - IN: - trans - the transition to use - always - if True, the transition will always occur, even if the user has disabled transitions - paired - Tom knows - clear - if True cleans out transient stuff at the end of an interaction - - OUT: - True if the user chose to interrupt the transition, - and False otherwise - """ - if renpy.game.context().init_phase: - raise Exception("With statements may not run while in init phase.") - - if renpy.config.skipping: - trans = None - - if not (renpy.game.preferences.transitions or always): - trans = None - - renpy.exports.mode('with') - - if isinstance(paired, dict): - paired = paired.get(None, None) - - if (trans is None) and (paired is None): - return - - if isinstance(trans, dict): - - for k, v in trans.items(): - if k is None: - continue - - renpy.exports.transition(v, layer=k) - - if None not in trans: - return - - trans = trans[None] - - return renpy.game.interface.do_with(trans, paired, clear=clear) - - renpy.exports.with_statement = mas_with_statement - def mas_find_target(self): """ This method tries to find an image by its reference. It can be a displayable or tuple. From 1167d3db7ea52d4d968a8380c430d6458bebac18 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 7 Sep 2022 00:31:47 +0300 Subject: [PATCH 159/180] update `mas_find_target` --- Monika After Story/game/definitions.rpy | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 1710554861..3ca09d4e72 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -208,6 +208,8 @@ python early: if renpy.config.debug: raise Exception(msg) + target = None # typing + args = [ ] while name: @@ -238,7 +240,7 @@ python early: target = renpy.display.image.images[name] #If we somehow failed, show the exception and return False - except: + except Exception: error("Image '%s' not found." % ' '.join(self.name)) return False @@ -246,17 +248,24 @@ python early: error("Image '%s' not found." % ' '.join(self.name)) return False + if self._args.name == name: + error("Image '{}' refers to itself.".format(' '.join(name))) + return False + + args += self._args.args + try: a = self._args.copy(name=name, args=args) self.target = target._duplicate(a) except Exception as e: - if renpy.config.debug: + if renpy.config.raise_image_exceptions and (renpy.config.debug or renpy.config.developer): raise error(str(e)) + return False - #Copy the old transform over. + # Copy the old transform over. new_transform = self.target._target() if isinstance(new_transform, renpy.display.transform.Transform): From 6aca9db24164eb35e584f1ce4b8fdf08a4349b90 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 7 Sep 2022 00:39:49 +0300 Subject: [PATCH 160/180] deprecate `MASImageData` --- Monika After Story/game/definitions.rpy | 37 +------------------ .../game/script-islands-event.rpy | 4 +- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 3ca09d4e72..e141e2e744 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -281,41 +281,8 @@ python early: renpy.display.image.ImageReference.find_target = mas_find_target - class MASImageData(renpy.display.im.ImageBase): - """ - NOTE: This DOES NOT support saving in persistent (pickling), - and it might be unsafe to do so. - - This image manipulator loads an image from binary data. - """ - def __init__(self, data, filename, **properties): - """ - Constructor - - IN: - data - string of bytes, giving the compressed image data in a standard - file format. - filename - "filename" associated with the image. This is used to provide a - hint to Ren'Py about the format of `data`. (It's not actually - loaded from disk.) - properties - additional props - """ - super(MASImageData, self).__init__(data, filename, **properties) - self.data = data - self.filename = filename - - def __unicode__(self): - return u"MASImageData({})".format(self.filename) - - def __repr__(self): - return str(self.__unicode__()) - - def __reduce__(self): - return (str, (self.filename,)) - - def load(self): - f = io.BytesIO(self.data) - return renpy.display.pgrender.load_image(f, self.filename) + # Deprecated, use im.Data directly + MASImageData = im.Data # uncomment this if you want syntax highlighting support on vim diff --git a/Monika After Story/game/script-islands-event.rpy b/Monika After Story/game/script-islands-event.rpy index 387b0fc97e..65d3949972 100644 --- a/Monika After Story/game/script-islands-event.rpy +++ b/Monika After Story/game/script-islands-event.rpy @@ -717,7 +717,7 @@ init -25 python in mas_island_event: for name, path_map in map_.items(): for sprite_type, path in path_map.items(): raw_data = zip_file.read(path) - img = store.MASImageData(raw_data, "{}_{}.png".format(name, sprite_type)) + img = store.im.Data(raw_data, "{}_{}.png".format(name, sprite_type)) path_map[sprite_type] = img try: @@ -732,7 +732,7 @@ init -25 python in mas_island_event: # Anim frames are handled a bit differently glitch_frames = tuple( - (store.MASImageData(zip_file.read(fn), fn + ".png") for fn in GLITCH_FPS) + (store.im.Data(zip_file.read(fn), fn + ".png") for fn in GLITCH_FPS) ) except Exception as e: From fa16c977d5d7a9ef2bac1fdd53f8a90194eba8d3 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 7 Sep 2022 03:16:56 +0300 Subject: [PATCH 161/180] definitions cleanup --- Monika After Story/game/definitions.rpy | 261 +++++++++--------------- 1 file changed, 102 insertions(+), 159 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index e141e2e744..aa71cf08e3 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -545,7 +545,7 @@ python early: start_date=None, end_date=None, unlock_date=None, -# diary_entry=None, + # diary_entry=None, rules=dict(), last_seen=None, years=None, @@ -562,12 +562,12 @@ python early: raise EventException("'per_eventdb' cannot be None") if action is not None and action not in EV_ACTIONS: raise EventException("'" + action + "' is not a valid action") -# if diary_entry is not None and len(diary_entry) > self.DIARY_LIMIT: -# raise Exception( -# ( -# "diary entry for {0} is longer than {1} characters" -# ).format(eventlabel, self.DIARY_LIMIT) -# ) + # if diary_entry is not None and len(diary_entry) > self.DIARY_LIMIT: + # raise Exception( + # ( + # "diary entry for {0} is longer than {1} characters" + # ).format(eventlabel, self.DIARY_LIMIT) + # ) if rules is None: raise Exception( "'{0}' - rules property cannot be None".format(eventlabel) @@ -673,7 +673,7 @@ python early: stored_data_list = list(stored_data_row) # first, check for lock existence - lock_entry = Event.INIT_LOCKDB.get(eventlabel, None) + lock_entry = self.INIT_LOCKDB.get(eventlabel, None) if lock_entry: @@ -687,7 +687,7 @@ python early: # if the lock exists, then iterate through the names # and only update items that are unlocked - for name,index in Event.T_EVENT_NAMES.items(): + for name,index in self.T_EVENT_NAMES.items(): if not lock_entry[index]: stored_data_list[index] = data_row[index] @@ -705,8 +705,8 @@ python early: # actaully this should be always self.prompt = prompt self.category = category -# self.diary_entry = diary_entry -# self.rules = rules + # self.diary_entry = diary_entry + # self.rules = rules self.years = years #self.sensitive = sensitive self.aff_range = aff_range @@ -718,11 +718,11 @@ python early: self.per_eventdb[self.eventlabel] = data_row # setup lock entry - Event.INIT_LOCKDB.setdefault(eventlabel, mas_init_lockdb_template) + self.INIT_LOCKDB.setdefault(eventlabel, mas_init_lockdb_template) # Cache conditional - if self.conditional is not None and self.conditional not in Event._conditional_cache: - Event._conditional_cache[self.conditional] = renpy.python.py_compile(self.conditional, "eval") + if self.conditional is not None and self.conditional not in self._conditional_cache: + self._conditional_cache[self.conditional] = renpy.python.py_compile(self.conditional, "eval") def __eq__(self, other): """ @@ -759,8 +759,8 @@ python early: value - the new value """ if name in self.N_EVENT_NAMES: - super(Event, self).__setattr__(name, value) -# self.__dict__[name] = value + super().__setattr__(name, value) + # self.__dict__[name] = value # otherwise, figure out the location of an attribute, then repack # a tup @@ -794,9 +794,9 @@ python early: elif ( name == "conditional" and value is not None - and value not in Event._conditional_cache + and value not in self._conditional_cache ): - Event._conditional_cache[value] = renpy.python.py_compile(value, "eval") + self._conditional_cache[value] = renpy.python.py_compile(value, "eval") # otherwise, repack the tuples data_row = list(data_row) @@ -829,7 +829,7 @@ python early: return data_row[attr_loc] else: - return super(Event, self).__getattribute__(name) + return super().__getattribute__(name) #repr override def __repr__(self): @@ -903,7 +903,11 @@ python early: if self.conditional is None: return True - return renpy.python.py_eval_bytecode(Event._conditional_cache[self.conditional], globals=globals, locals=locals) + return renpy.python.py_eval_bytecode( + self._conditional_cache[self.conditional], + globals=globals, + locals=locals + ) def canRepeat(self): """ @@ -942,7 +946,7 @@ python early: if not self.canRepeat(): return False - new_start, new_end, was_changed = Event._yearAdjustEV(self, force) + new_start, new_end, was_changed = self._yearAdjustEV(self, force) if was_changed: if self.isWithinRange(): @@ -1039,6 +1043,9 @@ python early: """ A method to validate conditionals + RAISES: + EventException + ASSUMES: mas_all_ev_db """ @@ -1049,7 +1056,7 @@ python early: except Exception as e: raise EventException( - "Failed to evaluate the '{0}' conditional for the event with the '{1}' label:\n{2}.".format( + "Failed to evaluate '{0}' conditional for the event '{1}':\n{2}.".format( ev.conditional, ev.eventlabel, traceback.format_exc() @@ -1072,8 +1079,8 @@ python early: """ return ev.shown_count - @staticmethod - def lockInit(name, ev=None, ev_label=None): + @classmethod + def lockInit(cls, name, ev=None, ev_label=None): """ Locks the property for a given event object or eventlabel. This will prevent the property from being overwritten on object @@ -1086,11 +1093,11 @@ python early: ev_label - event label of Event to property lock (Default: None) """ - Event._modifyInitLock(name, True, ev=ev, ev_label=ev_label) + cls._modifyInitLock(name, True, ev=ev, ev_label=ev_label) - @staticmethod - def unlockInit(name, ev=None, ev_label=None): + @classmethod + def unlockInit(cls, name, ev=None, ev_label=None): """ Unlocks the property for a given event object or event label. This will allow the property to be overwritten on object creation. @@ -1102,11 +1109,11 @@ python early: ev_label - event label of Event to property lock (Default: None) """ - Event._modifyInitLock(name, False, ev=ev, ev_label=ev_label) + cls._modifyInitLock(name, False, ev=ev, ev_label=ev_label) - @staticmethod - def _modifyInitLock(name, value, ev=None, ev_label=None): + @classmethod + def _modifyInitLock(cls, name, value, ev=None, ev_label=None): """ Modifies the init lock for a given event/eventlabel @@ -1123,7 +1130,7 @@ python early: return # check if we have a valid property - property_dex = Event.T_EVENT_NAMES.get(name, None) + property_dex = cls.T_EVENT_NAMES.get(name, None) if property_dex is None: return @@ -1132,13 +1139,13 @@ python early: ev_label = ev.eventlabel # now lock the property - lock_entry = list(Event.INIT_LOCKDB[ev_label]) + lock_entry = list(cls.INIT_LOCKDB[ev_label]) lock_entry[property_dex] = value - Event.INIT_LOCKDB[ev_label] = tuple(lock_entry) + cls.INIT_LOCKDB[ev_label] = tuple(lock_entry) - @staticmethod - def _verifyAndSetDatesEV(ev): + @classmethod + def _verifyAndSetDatesEV(cls, ev): """ Runs _verifyDatesEV and sets the event properties if change happens @@ -1148,7 +1155,7 @@ python early: RETURNS: was_changed """ - new_start, new_end, was_changed = Event._verifyDatesEV(ev) + new_start, new_end, was_changed = cls._verifyDatesEV(ev) if was_changed: ev.start_date = new_start ev.end_date = new_end @@ -1156,8 +1163,8 @@ python early: return was_changed - @staticmethod - def _verifyDatesEV(ev): + @classmethod + def _verifyDatesEV(cls, ev): """ _verifyDates, but for an Event object. @@ -1166,11 +1173,11 @@ python early: RETURNS: See _verifyDates """ - return Event._verifyDates(ev.start_date, ev.end_date, ev.years) + return cls._verifyDates(ev.start_date, ev.end_date, ev.years) - @staticmethod - def _yearAdjustEV(ev, force=False): + @classmethod + def _yearAdjustEV(cls, ev, force=False): """ _yearAdjust, but for an Event object @@ -1181,7 +1188,7 @@ python early: RETURNS: See _verifyDates """ - return Event._yearAdjust( + return cls._yearAdjust( ev.start_date, ev.end_date, ev.years, @@ -1189,8 +1196,8 @@ python early: ) - @staticmethod - def _verifyDates(_start, _end, _years): + @classmethod + def _verifyDates(cls, _start, _end, _years): """ Given start/end/_yeras, figure out the appropriate start and end dates. We use current datetime to figure this out. @@ -1215,7 +1222,7 @@ python early: return (_start, _end, False) # otherwise, we need to repeat - return Event._yearAdjust(_start, _end, _years) + return cls._yearAdjust(_start, _end, _years) @staticmethod @@ -1363,12 +1370,7 @@ python early: RETURNS: True if this event passes the filter, False if not """ - - # collections allow us to match all - from collections import Counter - # NOTE: this is done in an order to minimize branching. - # now lets filter if unlocked is not None and event.unlocked != unlocked: return False @@ -1424,8 +1426,8 @@ python early: # we've passed all the filtering rules somehow return True - @staticmethod - def filterEvents(events, **flt_args): + @classmethod + def filterEvents(cls, events, **flt_args): """ Filters the given events dict according to the given filters. HOW TO USE: Use ** to pass in a dict of filters. they must match @@ -1482,14 +1484,10 @@ python early: ): return events - # copy check -# if full_copy: -# from copy import deepcopy - # setup keys - cat_key = Event.FLT[0] - act_key = Event.FLT[4] - #sns_key = Event.FLT[8] + cat_key = cls.FLT[0] + act_key = cls.FLT[4] + #sns_key = cls.FLT[8] # validate filter rules category = flt_args.get(cat_key) @@ -1513,31 +1511,32 @@ python early: # python 2 for k,v in events.items(): # time to apply filtering rules - if Event._filterEvent(v, **flt_args): + if cls._filterEvent(v, **flt_args): filt_ev_dict[k] = v return filt_ev_dict @staticmethod def getSortedKeys(events, include_none=False): - # - # Returns a list of eventlables (keys) of the given dict of events - # sorted by the field unlock_date. The list is sorted in - # chronological order (newest first). Events with an unlock_date - # of None are not included unless include_none is True, in which - # case, Nones are put after everything else - # - # IN: - # events - dict of events of the following format: - # eventlabel: event object - # include_none - True means we include events that have None for - # unlock_date int he sorted key list, False means we dont - # (Default: False) - # - # RETURNS: - # list of eventlabels (keys), sorted in chronological order. - # OR: [] if the given events is empty or all unlock_date fields - # were None and include_none is False + """ + Returns a list of eventlables (keys) of the given dict of events + sorted by the field unlock_date. The list is sorted in + chronological order (newest first). Events with an unlock_date + of None are not included unless include_none is True, in which + case, Nones are put after everything else + + IN: + events - dict of events of the following format: + eventlabel: event object + include_none - True means we include events that have None for + unlock_date int he sorted key list, False means we dont + (Default: False) + + RETURNS: + list of eventlabels (keys), sorted in chronological order. + OR: [] if the given events is empty or all unlock_date fields + were None and include_none is False + """ # sanity check if not events or len(events) == 0: @@ -1576,8 +1575,8 @@ python early: return eventlabels - @staticmethod - def _checkEvent(ev, curr_time): + @classmethod + def _checkEvent(cls, ev, curr_time): """ Singular filter function for checkEvents @@ -1603,15 +1602,15 @@ python early: return False # check if valid action - if ev.action not in Event.ACTION_MAP: + if ev.action not in cls.ACTION_MAP: return False # success return True - @staticmethod - def checkEvents(ev_dict, rebuild_ev=True): + @classmethod + def checkEvents(cls, ev_dict, rebuild_ev=True): """ This acts as a combination of both checkConditoinal and checkCalendar @@ -1634,9 +1633,9 @@ python early: # - has None for date properties # indexing would be smarter. - if Event._checkEvent(ev, _now): + if cls._checkEvent(ev, _now): # perform action - Event._performAction( + cls._performAction( ev, unlock_time=_now, rebuild_ev=rebuild_ev @@ -1664,8 +1663,8 @@ python early: return MASFarewellRule.evaluate_rule(ev) - @staticmethod - def checkFarewellRules(events): + @classmethod + def checkFarewellRules(cls, events): """ Checks the event dict (farewells) against their own farewell specific rules, filters out those Events whose rule check return true. As for @@ -1690,62 +1689,7 @@ python early: for label, event in events.items(): # check if the event contains a MASFarewellRule and evaluate it - if Event._checkFarewellRule(event): - - if event.monikaWantsThisFirst(): - return {event.eventlabel: event} - - # add the event to our available events dict - available_events[label] = event - - # return the available events dict - return available_events - - #TODO: Depricate this - @staticmethod - def _checkAffectionRule(ev,keepNoRule=False): - """ - Checks the given event against its own affection specific rule. - - IN: - ev - event to check - - RETURNS: - True if this event passes its repeat rule, False otherwise - """ - return MASAffectionRule.evaluate_rule(ev,noRuleReturn=keepNoRule) - - #TODO: Depricate this - @staticmethod - def checkAffectionRules(events,keepNoRule=False): - """ - Checks the event dict against their own affection specific rules, - filters out those Events whose rule check return true. This rule - checks if current affection is inside the specified range contained - on the rule - - IN: - events - dict of events of the following format: - eventlabel: event object - keepNoRule - Boolean indicating wheter if it should keep - events that don't have an affection rule defined - - RETURNS: - A filtered dict containing the events that passed their own rules - - """ - # sanity check - if not events or len(events) == 0: - return None - - # prepare empty dict to store events that pass their own rules - available_events = dict() - - # iterate over each event in the given events dict - for label, event in events.items(): - - # check if the event contains a MASAffectionRule and evaluate it - if Event._checkAffectionRule(event,keepNoRule=keepNoRule): + if cls._checkFarewellRule(event): if event.monikaWantsThisFirst(): return {event.eventlabel: event} @@ -1756,9 +1700,8 @@ python early: # return the available events dict return available_events - - @staticmethod - def _performAction(ev, **kwargs): + @classmethod + def _performAction(cls, ev, **kwargs): """ Efficient / no checking action performing @@ -1768,19 +1711,19 @@ python early: ev - event we are performing action on **kwargs - keyword args to pass to action """ - Event.ACTION_MAP[ev.action](ev, **kwargs) + cls.ACTION_MAP[ev.action](ev, **kwargs) - @staticmethod - def performAction(ev, **kwargs): + @classmethod + def performAction(cls, ev, **kwargs): """ Performs the action of the given event IN: ev - event we are perfrming action on """ - if ev.action in Event.ACTION_MAP: - Event._performAction(ev, **kwargs) + if ev.action in cls.ACTION_MAP: + cls._performAction(ev, **kwargs) @staticmethod def _undoEVAction(ev): @@ -1897,12 +1840,12 @@ python early: """ super(renpy.Displayable, self).__init__() # setup -# self.idle_text = idle_text -# self.hover_text = hover_text -# self.disable_text = disable_text -# self.idle_back = idle_back -# self.hover_back = hover_back -# self.disable_back = disable_back + # self.idle_text = idle_text + # self.hover_text = hover_text + # self.disable_text = disable_text + # self.idle_back = idle_back + # self.hover_back = hover_back + # self.disable_back = disable_back self.xpos = xpos self.ypos = ypos self.width = width @@ -3312,7 +3255,7 @@ python early: if key.startswith(self.EX_PFX): return self.ex_props.get(key[self._EX_LEN:], None) - return super(MASExtraPropable, self).__getattr__(key) + return super().__getattr__(key) def __setattr__(self, key, value): if key.startswith(self.EX_PFX): @@ -3321,7 +3264,7 @@ python early: if len(stripped_key) > 0: self.ex_props[stripped_key] = value - super(MASExtraPropable, self).__setattr__(key, value) + super().__setattr__(key, value) def ex_has(self, key): """ From c44f0dfb75f3644bebd99fd7272db4e706dc8553 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 7 Sep 2022 03:28:18 +0300 Subject: [PATCH 162/180] ev hand cleanup --- Monika After Story/game/event-handler.rpy | 153 +++++++++++----------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index 4ba9a87593..5cd41be29d 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -1495,26 +1495,26 @@ init -1 python in evhand: _NT_CAT_PANE = namedtuple("_NT_CAT_PANE", "menu cats") # RIGHT PANE -# PREV_X = 30 + # PREV_X = 30 RIGHT_X = 1020 -# PREV_Y = 10 + # PREV_Y = 10 RIGHT_Y = 15 + 55 -# PREV_W = 300 + # PREV_W = 300 RIGHT_W = 250 RIGHT_H = 572 -# PREV_XALIGN = -0.08 + # PREV_XALIGN = -0.08 RIGHT_XALIGN = -0.10 RIGHT_AREA = (RIGHT_X, RIGHT_Y, RIGHT_W, RIGHT_H) # LEFT PANE -# MAIN_X = 360 + # MAIN_X = 360 LEFT_X = 740 -# MAIN_Y = 10 + # MAIN_Y = 10 LEFT_Y = RIGHT_Y -# MAIN_W = 300 + # MAIN_W = 300 LEFT_W = RIGHT_W LEFT_H = RIGHT_H -# MAIN_XALIGN = -0.08 + # MAIN_XALIGN = -0.08 LEFT_XALIGN = -0.10 LEFT_AREA = (LEFT_X, LEFT_Y, LEFT_W, LEFT_H) LEFT_EXTRA_SPACE = 68 @@ -1588,8 +1588,8 @@ init -1 python in evhand: self._eli ) - @staticmethod - def build(evl, *args): + @classmethod + def build(cls, evl, *args): """ Builds an ELI. @@ -1599,10 +1599,10 @@ init -1 python in evhand: RETURNS: EventListItem object """ - return EventListItem(EventListItem._build_raw(evl, *args)) + return cls(cls._build_raw(evl, *args)) - @staticmethod - def _build_raw(evl, *args): + @classmethod + def _build_raw(cls, evl, *args): """ Builds raw data for an ELI. @@ -1611,13 +1611,13 @@ init -1 python in evhand: RETURNS: raw data """ data = list( - (evl, ) + args + EventListItem.DEFAULT_VALUES[len(args):] + (evl, ) + args + cls.DEFAULT_VALUES[len(args):] ) # adjust context to be persistntable - ctx = data[EventListItem.IDX_CONTEXT] + ctx = data[cls.IDX_CONTEXT] if isinstance(ctx, store.MASEventContext): - data[EventListItem.IDX_CONTEXT] = ctx._to_dict() + data[cls.IDX_CONTEXT] = ctx._to_dict() return tuple(data) @@ -1951,7 +1951,7 @@ init python: ctx_data - context data directly from event list. Optional. (Default: None) """ - super(MASEventContext, self).__init__() + super().__init__() if ctx_data is not None: self._from_dict(ctx_data) @@ -1959,8 +1959,8 @@ init python: """ We don't allow types that cannot be saved to persistent """ - if MASEventContext.is_allowed_data(value): - super(MASEventContext, self).__setattr__(name, value) + if self.is_allowed_data(value): + super().__setattr__(name, value) @classmethod def is_allowed_data(cls, thing): @@ -2033,12 +2033,12 @@ init python: # current event functions - @staticmethod - def clear_current(): + @classmethod + def clear_current(cls): """ Clears the current event aka persistent eli data. """ - MASEventList._set_current(None) + cls._set_current(None) @staticmethod def load_current(): @@ -2076,39 +2076,39 @@ init python: persistent._mas_curr_eli_data = new_eli_data persistent.current_monikatopic = new_curr_moni_topic - @staticmethod - def sync_current(): + @classmethod + def sync_current(cls): """ Syncs the current event persistent vars, aka: - current_monikatopic - _mas_curr_eli_data """ - curr_eli = MASEventList.load_current() + curr_eli = cls.load_current() if curr_eli is None: if renpy.has_label(str(persistent.current_monikatopic)): # to handle unexpected uses, we'll build an eli for this # if this var is set but no eli data was found. - MASEventList._set_current(evhand.EventListItem.build( + cls._set_current(evhand.EventListItem.build( str(persistent.current_monikatopic) )) else: - MASEventList.clear_current() + cls.clear_current() else: - MASEventList._set_current(curr_eli) + cls._set_current(curr_eli) # event list functions - @staticmethod - def clean(): + @classmethod + def clean(cls): """ Cleans the event list and makes sure all events are of the appropriate length and have a valid label. """ - for index in MASEventList.rev_idx_iter(): + for index in cls.rev_idx_iter(): item_raw = persistent.event_list[index] # type check @@ -2159,8 +2159,8 @@ init python: mas_globals.event_unpause_dt = None return False - @staticmethod - def _next(): + @classmethod + def _next(cls): """ Gets the next event's data and its location in the event_list. This takes event restrictions into account, aka pausing and idle. @@ -2172,9 +2172,9 @@ init python: if len(persistent.event_list) < 1: return None, -1 - is_paused = MASEventList.is_paused() + is_paused = cls.is_paused() - for index, item in MASEventList.rev_enum_iter(): + for index, item in cls.rev_enum_iter(): ev = mas_getEV(item.evl) if ( @@ -2198,8 +2198,8 @@ init python: # no valid event available return None, -1 - @staticmethod - def peek(): + @classmethod + def peek(cls): """ Gets the EventListItem for the next event on the event list, but does NOT remove it. @@ -2212,10 +2212,10 @@ init python: RETURNS: EventListItem object for the next event, or None if no next event. """ - return MASEventList._next()[0] + return cls._next()[0] - @staticmethod - def pop(): + @classmethod + def pop(cls): """ Gets the EventListItem for the next event on the event list and removes the event from the event list. @@ -2228,7 +2228,7 @@ init python: RETURNS: EventListItem object for the next event """ - item, loc = MASEventList._next() + item, loc = cls._next() if item is None: return None @@ -2236,12 +2236,12 @@ init python: if 0 <= loc < len(persistent.event_list): # just in case persistent.event_list.pop(loc) - MASEventList._set_current(item) + cls._set_current(item) return item - @staticmethod - def push(event_label, skipeval=False, notify=False, context=None): + @classmethod + def push(cls, event_label, skipeval=False, notify=False, context=None): """ Pushes an event to the list - this will make the event trigger next unless something else is pushed. @@ -2259,7 +2259,7 @@ init python: (accessible via MASEventContext.get()) (Default: None) """ - MASEventList._push_eli(evhand.EventListItem.build( + cls._push_eli(evhand.EventListItem.build( event_label, notify, context @@ -2278,8 +2278,8 @@ init python: """ persistent.event_list.append(eli._raw()) - @staticmethod - def queue(event_label, notify=False, context=None): + @classmethod + def queue(cls, event_label, notify=False, context=None): """ Queues an event to the list - this will make the event trigger, but not right away unless the list is empty. @@ -2294,7 +2294,7 @@ init python: (accessible via MASEventContext.get()) (Default: None) """ - MASEventList._queue_eli(evhand.EventListItem.build( + cls._queue_eli(evhand.EventListItem.build( event_label, notify, context @@ -3428,33 +3428,32 @@ label prompts_categories(pool=True): # setup items main_items = no_cat_list - """ KEEP this for legacy purposes -# sorted_event_keys = Event.getSortedKeys(unlocked_events,include_none=True) + # KEEP this for legacy purposes + # sorted_event_keys = Event.getSortedKeys(unlocked_events,include_none=True) - prompt_category_menu = [] - #Make a list of categories + # prompt_category_menu = [] + # #Make a list of categories - #Make a list of all categories - subcategories=set([]) - for event in sorted_event_keys: - if unlocked_events[event].category is not None: - new_categories=set(unlocked_events[event].category).difference(set(current_category)) - subcategories=subcategories.union(new_categories) + # #Make a list of all categories + # subcategories=set([]) + # for event in sorted_event_keys: + # if unlocked_events[event].category is not None: + # new_categories=set(unlocked_events[event].category).difference(set(current_category)) + # subcategories=subcategories.union(new_categories) - subcategories = list(subcategories) - for category in sorted(subcategories, key=lambda s: s.lower()): - #Don't list additional subcategories if adding them wouldn't change the same you are looking at - test_unlock = Event.filterEvents(evhand.event_database,full_copy=True,category=[False,current_category+[category]],unlocked=True) + # subcategories = list(subcategories) + # for category in sorted(subcategories, key=lambda s: s.lower()): + # #Don't list additional subcategories if adding them wouldn't change the same you are looking at + # test_unlock = Event.filterEvents(evhand.event_database,full_copy=True,category=[False,current_category+[category]],unlocked=True) - if len(test_unlock) != len(sorted_event_keys): - prompt_category_menu.append([category.capitalize() + "...",category]) + # if len(test_unlock) != len(sorted_event_keys): + # prompt_category_menu.append([category.capitalize() + "...",category]) - #If we do have a category picked, make a list of the keys - if sorted_event_keys is not None: - for event in sorted_event_keys: - prompt_category_menu.append([unlocked_events[event].prompt,event]) - """ + # #If we do have a category picked, make a list of the keys + # if sorted_event_keys is not None: + # for event in sorted_event_keys: + # prompt_category_menu.append([unlocked_events[event].prompt,event]) call screen twopane_scrollable_menu(prev_items, main_items, evhand.LEFT_AREA, evhand.LEFT_XALIGN, evhand.RIGHT_AREA, evhand.RIGHT_XALIGN, len(current_category)) nopredict @@ -3467,15 +3466,15 @@ label prompts_categories(pool=True): current_category.pop() current_category.append(_return) -# TODO: if we have subcategories, this needs to be setup properly -# elif _return in main_cats: - # we selected a category in the main pane -# $ current_category.append(_return) -# $ cat_lists.append(main_pane) -# $ is_root = False + # TODO: if we have subcategories, this needs to be setup properly + # elif _return in main_cats: + # we selected a category in the main pane + # $ current_category.append(_return) + # $ cat_lists.append(main_pane) + # $ is_root = False -# elif _return == -2: # Thats enough for now -# $picked_event = True + # elif _return == -2: # Thats enough for now + # $picked_event = True elif _return == -1: # go back if len(current_category) > 0: From 54cb3ffc3dcb5ea5cd2d46135ad2f863c30d6ad1 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 8 Sep 2022 18:36:50 +0300 Subject: [PATCH 163/180] ignore `navigation.json` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9ea0cd5b96..7897a85f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ log zzzz* # Build +navigation.json Monika_After_Story-[0-9]*.[0-9]*.[0-9]*-dists From eb8b7956ad5fbf5f998f620577f52d16fbf972bb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 8 Sep 2022 18:48:21 +0300 Subject: [PATCH 164/180] move config vars into config file --- Monika After Story/game/0config.rpy | 23 +++++++++++++++++++++++ Monika After Story/game/definitions.rpy | 24 ------------------------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index 398ef2aa80..71eaba8efd 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -35,6 +35,29 @@ python early: renpy.config.save_directory = "Monika After Story" + ### R7+ Config Var adjustments + ##7.3.3 + #Only devs need this + renpy.config.report_extraneous_attributes = False + + ##7.3.0 + renpy.config.keyword_after_python = True + + ##7.1.1 + #Fix menu textbox issues + renpy.config.menu_showed_window = True + #Fix textbox sometimes disappearing + renpy.config.window_auto_show = ["say"] + #Fix textbox flickering + renpy.config.window_auto_hide = ["scene", "call screen"] + + ##7.0 + #Fixes spaceroom masks from restarting every interaction + renpy.config.replay_movie_sprites = False + + ##6.99.13 + renpy.config.atl_one_frame = False + init -1200 python: ## Sounds and music ############################################################ diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index aa71cf08e3..e5b6aff0c4 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -3,30 +3,6 @@ define persistent.demo = False define config.developer = False # define persistent.steam = "steamapps" in config.basedir.lower() -###R7+ Config Var adjustments -init 999: - ##7.3.3 - #Only devs need this - define config.report_extraneous_attributes = False - - ##7.3.0 - define config.keyword_after_python = True - - ##7.1.1 - #Fix menu textbox issues - define config.menu_showed_window = True - #Fix textbox sometimes disappearing - define config.window_auto_show = ["say"] - #Fix textbox flickering - define config.window_auto_hide = ["scene", "call screen"] - - ##7.0 - #Fixes spaceroom masks from restarting every interaction - define config.replay_movie_sprites = False - - ##6.99.13 - define config.atl_one_frame = False - python early: # We want these to be available globally, please don't remove From efef6bc38c546fedbfb034f92adfd67f5cc7fc24 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 8 Sep 2022 19:15:00 +0300 Subject: [PATCH 165/180] move another config var --- Monika After Story/game/0config.rpy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index 71eaba8efd..b4b51aeee3 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -36,6 +36,8 @@ python early: renpy.config.save_directory = "Monika After Story" ### R7+ Config Var adjustments + ## 7.4.11 + renpy.config.mouse_focus_clickthrough = True ##7.3.3 #Only devs need this renpy.config.report_extraneous_attributes = False @@ -147,6 +149,5 @@ define config.main_menu_music = audio.t1 define config.window_show_transition = dissolve_textbox define config.window_hide_transition = dissolve_textbox -define config.mouse_focus_clickthrough = True init python: config.per_frame_screens.append("_trace_screen") From 1e4e5063b223252c8a16a3c862dee348f679befd Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Fri, 16 Sep 2022 06:10:54 +0300 Subject: [PATCH 166/180] update version --- Monika After Story/game/0config.rpy | 2 +- Monika After Story/game/updates.rpy | 6 ++++++ Monika After Story/game/updates_topics.rpy | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index b4b51aeee3..c6c63ee344 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -14,7 +14,7 @@ python early: renpy.config.name = "Monika After Story" ## The version of the game. - renpy.config.version = "0.12.9" + renpy.config.version = "0.13.0" #Triple space suffix to avoid potential issues with same names in window title config.window_title = "Monika After Story " diff --git a/Monika After Story/game/updates.rpy b/Monika After Story/game/updates.rpy index 2934672375..34c88ac816 100644 --- a/Monika After Story/game/updates.rpy +++ b/Monika After Story/game/updates.rpy @@ -374,6 +374,12 @@ label v0_3_1(version=version): # 0.3.1 # non generic updates go here +# 0.13.0 aka RenPy8/Python3 +label v0_13_0(version="v0_13_0"): + python hide: + pass + return + # 0.12.9.1 label v0_12_9_1(version="v0_12_9_1"): python hide: diff --git a/Monika After Story/game/updates_topics.rpy b/Monika After Story/game/updates_topics.rpy index de76ace008..6241fa80ce 100644 --- a/Monika After Story/game/updates_topics.rpy +++ b/Monika After Story/game/updates_topics.rpy @@ -91,7 +91,8 @@ init -2 python in mas_versions: # use dot notation to separate the parts of a version add_steps({ - #"0.12.9.1": ("0.12.9", "0.12.8.6"), + # "0.13.0": "0.12.9.1", + "0.12.9.1": ("0.12.9", "0.12.8.6"), "0.12.8.6": ("0.12.8.5", "0.12.8.4", "0.12.8.3"), "0.12.8.3": ("0.12.8.2", "0.12.8.1"), "0.12.8.1": "0.12.8", From d7bc1315e5679d8db7b591345ec9eca88f70af60 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Sat, 15 Oct 2022 10:21:41 +0300 Subject: [PATCH 167/180] temp fix to renpy bug --- Monika After Story/game/0statements.rpy | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Monika After Story/game/0statements.rpy b/Monika After Story/game/0statements.rpy index 9978c16027..60f70dde95 100644 --- a/Monika After Story/game/0statements.rpy +++ b/Monika After Story/game/0statements.rpy @@ -1,7 +1,8 @@ python early in mas_statements: from collections import namedtuple - __JumpWithArgsParseData = namedtuple("__JumpWithArgsParseData", ("label", "is_expression", "arg_info")) + # Consider this being fully private member of this namespace + _JumpWithArgsParseData = namedtuple("_JumpWithArgsParseData", ("label", "is_expression", "arg_info")) def __jump_with_args(label, args, kwargs): @@ -37,7 +38,7 @@ python early in mas_statements: NOTE: may raise exceptions IN: - parsed_data - __JumpWithArgsParseData for this statement + parsed_data - _JumpWithArgsParseData for this statement OUT: str @@ -55,7 +56,7 @@ python early in mas_statements: lex - the Lexer object OUT: - __JumpWithArgsParseData + _JumpWithArgsParseData """ lex.expect_noblock("jarg") @@ -73,14 +74,14 @@ python early in mas_statements: lex.expect_eol() lex.advance() - return __JumpWithArgsParseData(label_, is_expression, arg_info) + return _JumpWithArgsParseData(label_, is_expression, arg_info) def __execute_jump_with_args(parsed_data): """ Executes the jump_with_args statement IN: - parsed_data - __JumpWithArgsParseData for this statement + parsed_data - _JumpWithArgsParseData for this statement """ label_ = __get_label(parsed_data) @@ -97,7 +98,7 @@ python early in mas_statements: Predicts the jump_with_args statement IN: - parsed_data - __JumpWithArgsParseData for this statement + parsed_data - _JumpWithArgsParseData for this statement """ try: label_ = __get_label(parsed_data) @@ -114,7 +115,7 @@ python early in mas_statements: A lint function for the jump_with_args statement IN: - parsed_data - __JumpWithArgsParseData for this statement + parsed_data - _JumpWithArgsParseData for this statement """ try: label_ = __get_label(parsed_data) @@ -127,7 +128,7 @@ python early in mas_statements: # Define the new statement renpy.register_statement( - "jarg", + "jarg", parse=__parse_jump_with_args, execute=__execute_jump_with_args, predict=__predict_jump_with_args, From cb940db1068c9ffe63048e2318899f2913435829 Mon Sep 17 00:00:00 2001 From: Legendkiller21 <42556779+Legendkiller21@users.noreply.github.com> Date: Sun, 16 Oct 2022 01:29:20 +0300 Subject: [PATCH 168/180] smol wrs fix --- Monika After Story/game/script-ch30.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index 63a2cebe34..d0557e1605 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -1324,7 +1324,7 @@ init 999 python in mas_reset: Runs reset code for window reactions """ #set MAS window global - mas_windowutils._setMASWindow() + mas_windowutils._setMASWindow_Linux() @ch30_reset(-600) From effa11835035ed16a52729f05825786d778414a4 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:51:00 +0300 Subject: [PATCH 169/180] remove missed `iteritems` --- Monika After Story/game/script-ch30.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index dd8c0db8fa..b73585878d 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -940,7 +940,7 @@ init 999 python in mas_reset: else: store.mas_lockGame("nou") - for game_name, game_startlabel in game_unlock_db.iteritems(): + for game_name, game_startlabel in game_unlock_db.items(): # unlock if we've seen the label if store.mas_getEVL_shown_count(game_startlabel) > 0: store.mas_unlockGame(game_name) From 6bd090de0c090974bc4801ff86aea9effdd1f845 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 2 Nov 2022 19:33:43 +0300 Subject: [PATCH 170/180] deprecate `MASAudioData` --- Monika After Story/game/definitions.rpy | 168 +----------------- .../game/script-islands-event.rpy | 2 +- 2 files changed, 3 insertions(+), 167 deletions(-) diff --git a/Monika After Story/game/definitions.rpy b/Monika After Story/game/definitions.rpy index 39c22100e7..c26da08161 100644 --- a/Monika After Story/game/definitions.rpy +++ b/Monika After Story/game/definitions.rpy @@ -260,172 +260,8 @@ python early: # Deprecated, use im.Data directly MASImageData = im.Data - - class MASAudioData(unicode): - """ - NOTE: This is temporal plaster-fix to renpy, use on your own risk, - this class and all support for it will be completely gone with r8 - NOTE: This DOES NOT support saving in persistent (pickling), - and it might be unsafe to do so. - - This loads audio from binary data - """ - - def __new__(cls, data, filename): - rv = unicode.__new__(cls, filename) - rv.data = data - return rv - - def __init__(self, data, filename): - self.filename = filename - - def __reduce__(self): - # Pickle as a str is safer - return (str, (self.filename, )) - - def __mas_periodic_override(self): - """ - This is the periodic call that causes this channel to load new stuff - into its queues, if necessary. - """ - - # Update the channel volume. - vol = self.chan_volume * renpy.game.preferences.volumes[self.mixer] - - if vol != self.actual_volume: - renpy.audio.renpysound.set_volume(self.number, vol) - self.actual_volume = vol - - # This should be set from something that checks to see if our - # mixer is muted. - force_stop = self.context.force_stop or (renpy.game.preferences.mute[self.mixer] and self.stop_on_mute) - - if self.playing and force_stop: - renpy.audio.renpysound.stop(self.number) - self.playing = False - self.wait_stop = False - - if force_stop: - if self.loop: - self.queue = self.queue[-len(self.loop):] - else: - self.queue = [ ] - return - - # Should we do the callback? - do_callback = False - - topq = None - - # This has been modified so we only queue a single sound file - # per call, to prevent memory leaks with really short sound - # files. So this loop will only execute once, in practice. - while True: - - depth = renpy.audio.renpysound.queue_depth(self.number) - - if depth == 0: - self.wait_stop = False - self.playing = False - - # Need to check this, so we don't do pointless work. - if not self.queue: - break - - # If the pcm_queue is full, then we can't queue - # anything, regardless of if it is midi or pcm. - if depth >= 2: - break - - # If we can't buffer things, and we're playing something - # give up here. - if not self.buffer_queue and depth >= 1: - break - - # We can't queue anything if the depth is > 0 and we're - # waiting for a synchro_start. - if self.synchro_start and depth: - break - - # If the queue is full, return. - if renpy.audio.renpysound.queue_depth(self.number) >= 2: - break - - # Otherwise, we might be able to enqueue something. - topq = self.queue.pop(0) - - # Blacklist of old file formats we used to support, but we now - # ignore. - lfn = topq.filename.lower() + self.file_suffix.lower() - for i in (".mod", ".xm", ".mid", ".midi"): - if lfn.endswith(i): - topq = None - - if not topq: - continue - - try: - filename, start, end = self.split_filename(topq.filename, topq.loop) - - if (end >= 0) and ((end - start) <= 0) and self.queue: - continue - - if isinstance(topq.filename, MASAudioData): - topf = io.BytesIO(topq.filename.data) - else: - topf = renpy.audio.audio.load(self.file_prefix + filename + self.file_suffix) - - renpy.audio.renpysound.set_video(self.number, self.movie) - - if depth == 0: - renpy.audio.renpysound.play(self.number, topf, topq.filename, paused=self.synchro_start, fadein=topq.fadein, tight=topq.tight, start=start, end=end) - else: - renpy.audio.renpysound.queue(self.number, topf, topq.filename, fadein=topq.fadein, tight=topq.tight, start=start, end=end) - - self.playing = True - - except: - - # If playing failed, remove topq.filename from self.loop - # so we don't keep trying. - while topq.filename in self.loop: - self.loop.remove(topq.filename) - - if renpy.config.debug_sound and not renpy.game.after_rollback: - raise - else: - return - - break - - if self.loop and not self.queue: - for i in self.loop: - if topq is not None: - newq = renpy.audio.audio.QueueEntry(i, 0, topq.tight, True) - else: - newq = renpy.audio.audio.QueueEntry(i, 0, False, True) - - self.queue.append(newq) - else: - do_callback = True - - # Queue empty callback. - if do_callback and self.callback: - self.callback() # E1102 - - # global global_pause - want_pause = self.context.pause or renpy.audio.audio.global_pause - - if self.paused != want_pause: - - if want_pause: - self.pause() - else: - self.unpause() - - self.paused = want_pause - - renpy.audio.audio.Channel.periodic = __mas_periodic_override + # Deprecated, use AudioData + MASAudioData = AudioData # uncomment this if you want syntax highlighting support on vim # init -1 python: diff --git a/Monika After Story/game/script-islands-event.rpy b/Monika After Story/game/script-islands-event.rpy index 59a9a7dc0c..3c363ce194 100644 --- a/Monika After Story/game/script-islands-event.rpy +++ b/Monika After Story/game/script-islands-event.rpy @@ -1077,7 +1077,7 @@ init -25 python in mas_island_event: isly_data = IslandsDataDefinition.getDataFor("other_isly") if isly_data: for fn, fp in isly_data.fp_map.items(): - audio_data = store.MASAudioData(zip_file.read(fp), fp + ".ogg") + audio_data = AudioData(zip_file.read(fp), fp + ".ogg") setattr(store.audio, "isld_isly_" + fn, audio_data) except Exception as e: From 7521d5db284be3eab48af9dae0b02eeb9025b6b1 Mon Sep 17 00:00:00 2001 From: Legendkiller21 <42556779+Legendkiller21@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:34:38 +0200 Subject: [PATCH 171/180] decode api keys --- Monika After Story/game/zz_apikeys.rpy | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/zz_apikeys.rpy b/Monika After Story/game/zz_apikeys.rpy index 87766fa635..b659c43f88 100644 --- a/Monika After Story/game/zz_apikeys.rpy +++ b/Monika After Story/game/zz_apikeys.rpy @@ -494,8 +494,22 @@ init -980 python in mas_api_keys: # null key is not counted return - # clear newlines - new_key = clean_key(new_key) + try: + # clear newlines + new_key = clean_key(new_key.decode("utf-8")) + + except UnicodeDecodeError as e: + # log the error + store.mas_utils.mas_log.error("Failed to decode API key: {}".format(e)) + + # show message box + store.renpy.show_screen( + "dialog", + message="Failed to decode API key.", + ok_action=store.Hide("dialog") + ) + # can't get a clean key, return here + return # on change onchange_rv = _run_on_change(feature, new_key) From 8ecd61f7505ee6c4f0b97e973ac156487ec1984f Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 3 Nov 2022 19:52:24 +0300 Subject: [PATCH 172/180] pack scripts into an archive --- Monika After Story/game/options.rpy | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index d2fe742a11..be2cef604b 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -124,16 +124,16 @@ init python: ##This tells Renpy to build an updater file build.include_update = True - ## This is the archive of data for your mod - #build.archive(build.name, "all") + ## Define the archives to use + build.archive("scripts", "all") ## These files will be included in the package # Add mod assets build.classify("game/mod_assets/**", "all") build.classify("game/gui/**", "all") # Add scripts in the game folder - # build.classify("game/*.rpy", "all") # Optional, includes source - build.classify("game/*.rpyc", "all") + # build.classify("game/*.rpy", "scripts")# Optional, includes source + build.classify("game/*.rpyc", "scripts") # Add python packages build.classify("game/python-packages/**", "all") # Add README @@ -151,7 +151,6 @@ init python: build.classify("log/**", None) build.classify("*.log", None) - build.classify("game/mod_assets/api_keys.json", None) build.classify("**.pem", None) ## Files matching documentation patterns are duplicated in a mac app build, From f15ed8b8f2ec0e1c0fefeb5791bdeaf5f22ff2ab Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:29:21 +0300 Subject: [PATCH 173/180] replace renpy `object` with `python_object` renpy base class doesn't support `__slots__` because imagine doing things properly --- Monika After Story/game/0utils.rpy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/0utils.rpy b/Monika After Story/game/0utils.rpy index 5c62a62928..f2d0b344da 100644 --- a/Monika After Story/game/0utils.rpy +++ b/Monika After Story/game/0utils.rpy @@ -370,7 +370,7 @@ python early in mas_utils: from store import mas_logging - + def init_mas_log(): """ Initializes the MAS log, or gets it if its already init. @@ -490,7 +490,7 @@ python early in mas_utils: mas_log.debug("".join(traceback.format_stack())) - class IsolatedFlexProp(object): + class IsolatedFlexProp(python_object): """ class that supports flexible attributes. all attributes that are set are stored in a @@ -747,4 +747,3 @@ python early in mas_utils: return int(value) except: return default - From a73b67359d9b419103328f0be3558394d58084bb Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Sun, 13 Nov 2022 15:02:28 +0300 Subject: [PATCH 174/180] cleanup old code this code slowed down initial loading and we don't even use it --- Monika After Story/game/script-ch30.rpy | 44 +++++++------------------ 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index b73585878d..dea7e7e046 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -9,7 +9,6 @@ default persistent.monika_kill = None # Whether or not you launched the mod before default persistent.first_run = True default persistent.rejected_monika = None -default initial_monika_file_check = None define modoorg.CHANCE = 20 define mas_in_intro_flow = False @@ -307,6 +306,16 @@ define MAS_PRONOUN_GENDER_MAP = { "hero": {"M": "hero", "F": "heroine", "X": "hero"} } +init 1 python: + currentuser = mas_get_user() + # name changes if necessary + if not currentuser: + currentuser = persistent.playername + if not persistent.mcname: + persistent.mcname = currentuser + + mcname = persistent.mcname + init python: import subprocess import os @@ -315,37 +324,6 @@ init python: import store.songs as songs import store.hkb_button as hkb_button import store.mas_globals as mas_globals - process_list = [] - currentuser = None # start if with no currentuser - if renpy.windows: - try: - process_list = subprocess.check_output("wmic process get Description", shell=True).lower().replace("\r", "").replace(" ", "").split("\n") - except: - pass - try: - for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'): - user = os.environ.get(name) - if user: - currentuser = user - except: - pass - - try: - renpy.file("../characters/monika.chr") - initial_monika_file_check = True - except: - #Monika will mention that you don't have a char file in ch30_main instead - pass - - - # name changes if necessary - if not currentuser or len(currentuser) == 0: - currentuser = persistent.playername - if not persistent.mcname or len(persistent.mcname) == 0: - persistent.mcname = currentuser - mcname = currentuser - else: - mcname = persistent.mcname # we need a new music channel for background audio (like rain!) # this uses the amb (ambient) mixer. @@ -380,7 +358,7 @@ init python: renpy.jump("mas_pick_a_game") - def mas_getuser(): + def mas_get_user(): """ Attempts to get the current user From 31254fcdf18052ad0fe800b191b0999f0d3fe3a5 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Tue, 6 Dec 2022 21:25:53 +0300 Subject: [PATCH 175/180] fix crash --- Monika After Story/game/script-stories.rpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/script-stories.rpy b/Monika After Story/game/script-stories.rpy index 94c074aaf8..d4cea4bc03 100644 --- a/Monika After Story/game/script-stories.rpy +++ b/Monika After Story/game/script-stories.rpy @@ -100,7 +100,7 @@ init -1 python in mas_stories: story_type - story type to get OUT: - list of locked stories for the given story type + dict of locked stories for the given story type """ return store.Event.filterEvents( story_database, @@ -123,7 +123,7 @@ init -1 python in mas_stories: stories = get_new_stories_for_type(story_type) #Grab one of the stories - story = renpy.random.choice(stories.values()) + story = renpy.random.choice(tuple(stories.values())) #Unlock and return its eventlabel story.unlocked = True From c920cbccd1498626fa8c0e796b4ac55a15ac9379 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 10 Jun 2023 18:09:52 -0400 Subject: [PATCH 176/180] fix some syntax warnings --- Monika After Story/game/chess.rpy | 4 ++-- Monika After Story/game/script-holidays.rpy | 3 +-- Monika After Story/game/special-effects.rpy | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index ccd0a97acd..2c1bd24397 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -916,8 +916,8 @@ label mas_chess_remenu: "color_select": { "options": [ ("White", True, False, is_player_white), - ("Black", False, False, is_player_white is False), - ("Let's draw lots!", 0, False, is_player_white is 0) #Is check here specifically for states + ("Black", False, False, is_player_white == False), + ("Let's draw lots!", 0, False, is_player_white == 0) #Is check here specifically for states ], "final_items": [ ("Gamemode", "gamemode_select", False, False, 20), diff --git a/Monika After Story/game/script-holidays.rpy b/Monika After Story/game/script-holidays.rpy index 5395764784..b28549e6f7 100644 --- a/Monika After Story/game/script-holidays.rpy +++ b/Monika After Story/game/script-holidays.rpy @@ -31,7 +31,6 @@ init 10 python: if key is None: key = datetime.date.today() - persistent._mas_event_clothes_map[key] = clothes.name #We also unlock the event clothes selector here @@ -1350,7 +1349,7 @@ label mas_o31_lingerie_end: mas_lockEVL("greeting_o31_lingerie", "GRE") # restart song/sounds that were playing before event - if globals().get("curr_song", -1) is not -1 and curr_song != store.songs.FP_MONIKA_LULLABY: + if globals().get("curr_song", -1) != -1 and curr_song != store.songs.FP_MONIKA_LULLABY: mas_play_song(curr_song, 1.0) else: mas_play_song(None, 1.0) diff --git a/Monika After Story/game/special-effects.rpy b/Monika After Story/game/special-effects.rpy index 10e92cb377..76f1ac7027 100644 --- a/Monika After Story/game/special-effects.rpy +++ b/Monika After Story/game/special-effects.rpy @@ -183,7 +183,7 @@ init -500 python in mas_parallax: @property def children(self): - return [decal for decal in self._decals if decal is not self._base] + return [decal for decal in self._decals if decal != self._base] @property def debug(self): @@ -1359,7 +1359,7 @@ label mas_timed_text_events_wrapup: mas_DropShield_timedtext() # restart song/sounds that were playing before event - if globals().get("curr_song", -1) is not -1 and curr_song != store.songs.FP_MONIKA_LULLABY: + if globals().get("curr_song", -1) != -1 and curr_song != store.songs.FP_MONIKA_LULLABY: mas_play_song(curr_song, 1.0) else: mas_play_song(None, 1.0) From beab3382ec1453ef08794660a307e40dd30ec3be Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 10 Jun 2023 19:55:51 -0400 Subject: [PATCH 177/180] fix is checks --- Monika After Story/game/chess.rpy | 4 ++-- Monika After Story/game/special-effects.rpy | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Monika After Story/game/chess.rpy b/Monika After Story/game/chess.rpy index 2c1bd24397..ccd0a97acd 100644 --- a/Monika After Story/game/chess.rpy +++ b/Monika After Story/game/chess.rpy @@ -916,8 +916,8 @@ label mas_chess_remenu: "color_select": { "options": [ ("White", True, False, is_player_white), - ("Black", False, False, is_player_white == False), - ("Let's draw lots!", 0, False, is_player_white == 0) #Is check here specifically for states + ("Black", False, False, is_player_white is False), + ("Let's draw lots!", 0, False, is_player_white is 0) #Is check here specifically for states ], "final_items": [ ("Gamemode", "gamemode_select", False, False, 20), diff --git a/Monika After Story/game/special-effects.rpy b/Monika After Story/game/special-effects.rpy index 76f1ac7027..810a270b9f 100644 --- a/Monika After Story/game/special-effects.rpy +++ b/Monika After Story/game/special-effects.rpy @@ -183,7 +183,8 @@ init -500 python in mas_parallax: @property def children(self): - return [decal for decal in self._decals if decal != self._base] + # Is not here because instance checks are mandatory + return [decal for decal in self._decals if decal is not self._base] @property def debug(self): From db9023aeb6c6e1404cdab8bc6ca645ace21b4726 Mon Sep 17 00:00:00 2001 From: potato Date: Sat, 17 Feb 2024 16:55:06 -0600 Subject: [PATCH 178/180] deprecate fix --- Monika After Story/game/event-handler.rpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/game/event-handler.rpy b/Monika After Story/game/event-handler.rpy index fbf9ab4abe..642f9ad2fe 100644 --- a/Monika After Story/game/event-handler.rpy +++ b/Monika After Story/game/event-handler.rpy @@ -2618,7 +2618,7 @@ init python: MASEventList.queue(*args, **kwargs) - @store.mas_utils.deprecated("mas_unlockEventLabel") + @store.mas_utils.deprecated(use_instead="mas_unlockEventLabel") def unlockEventLabel(evlabel, eventdb=evhand.event_database): """ NOTE: DEPRECATED From c097a96ea32960ba01c959ae70d95d9f9d836c01 Mon Sep 17 00:00:00 2001 From: multimokia Date: Sat, 17 Feb 2024 21:14:59 -0500 Subject: [PATCH 179/180] add override for persistent signature checks --- Monika After Story/game/overrides.rpy | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/overrides.rpy b/Monika After Story/game/overrides.rpy index a10f494a47..9e1e196997 100644 --- a/Monika After Story/game/overrides.rpy +++ b/Monika After Story/game/overrides.rpy @@ -20,4 +20,13 @@ init -10 python: ## You'll need a block like this for creator defined screen language ## Don't use this unless you know you need it python early in mas_overrides: - pass + def verify_data_override(data, signatures, check_verifying=True): + """ + Verify the data in a save token. + + Originally, this function is used to check against a checksum to verify the persistent should be loaded + But because we want to allow anyone be able to migrate and transfer their data, we will just return True + """ + return True + + renpy.savetoken.verify_data = verify_data_override From ecab7b4da95b50a6728aab3533a427c97a79dfaf Mon Sep 17 00:00:00 2001 From: potato Date: Sat, 17 Feb 2024 21:03:19 -0600 Subject: [PATCH 180/180] add savelocation.init override --- Monika After Story/game/overrides.rpy | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Monika After Story/game/overrides.rpy b/Monika After Story/game/overrides.rpy index 9e1e196997..4283d4c6e7 100644 --- a/Monika After Story/game/overrides.rpy +++ b/Monika After Story/game/overrides.rpy @@ -20,6 +20,12 @@ init -10 python: ## You'll need a block like this for creator defined screen language ## Don't use this unless you know you need it python early in mas_overrides: + import threading + + import renpy + import renpy.savelocation as savelocation + + def verify_data_override(data, signatures, check_verifying=True): """ Verify the data in a save token. @@ -30,3 +36,33 @@ python early in mas_overrides: return True renpy.savetoken.verify_data = verify_data_override + + + def savelocation_init_override(): + """ + Run **SOME** of the stuff savelocation.init runs + + basically we trying to keep saves in the AppData/equivalent folder + to make backups/restoring easier. + + The only difference here is that this skips over game savedirs and + 'extra' save dirs (so just omissions) + """ + savelocation.quit() + savelocation.quit_scan_thread = False + + location = savelocation.MultiLocation() + + location.add(savelocation.FileLocation(renpy.config.savedir)) + + location.scan() + + renpy.loadsave.location = location + + if not renpy.emscripten: + savelocation.scan_thread = threading.Thread(target=savelocation.run_scan_thread) + savelocation.scan_thread.start() + + savelocation.init = savelocation_init_override + +

E{BJbwXI7SHRrmVenN@Wb8)a2# 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 ABLDyZ diff --git a/Monika After Story/game/python-packages/pywintypes27.dll b/Monika After Story/game/python-packages/pywintypes27.dll deleted file mode 100644 index 99f715ed8045679de01bd2a1ff2455d94baa7a9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110592 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 aW&Rb2u&svgu@1WuJ81p?@8|#77Wh8{K?-gF diff --git a/Monika After Story/game/python-packages/win32gui.pyd b/Monika After Story/game/python-packages/win32gui.pyd deleted file mode 100644 index 63d2734c7d2f0296b43237982d7ce412f55f1d16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167936 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 From 1ca26d32ac6ce6ab04fd11e6da319e4fff49b187 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 06:31:31 +0300 Subject: [PATCH 128/180] add `winnie32api` --- .../python-packages/winnie32api/__init__.py | 27 + .../python-packages/winnie32api/common.py | 79 +++ .../game/python-packages/winnie32api/mouse.py | 23 + .../python-packages/winnie32api/notifs.py | 527 ++++++++++++++++++ .../python-packages/winnie32api/windows.py | 129 +++++ 5 files changed, 785 insertions(+) create mode 100644 Monika After Story/game/python-packages/winnie32api/__init__.py create mode 100644 Monika After Story/game/python-packages/winnie32api/common.py create mode 100644 Monika After Story/game/python-packages/winnie32api/mouse.py create mode 100644 Monika After Story/game/python-packages/winnie32api/notifs.py create mode 100644 Monika After Story/game/python-packages/winnie32api/windows.py diff --git a/Monika After Story/game/python-packages/winnie32api/__init__.py b/Monika After Story/game/python-packages/winnie32api/__init__.py new file mode 100644 index 0000000000..702dbc82d2 --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/__init__.py @@ -0,0 +1,27 @@ +""" +winnie32api - minimalistic Windows API + +Provides a small number of utils, allowing to not include big dependencies like pywin32/win32api +""" + +from __future__ import annotations + +__title__ = "winnie32api" +__author__ = "Booplicate" +__version__ = "0.0.1" + +import ctypes + +from .mouse import ( + get_screen_mouse_pos +) +from .windows import ( + get_hwnd_by_title, + get_window_title, + get_window_rect, + get_active_window_hwnd, + get_active_window_title, + get_active_window_rect +) + +from .notifs import WindowsNotifManager, WindowsNotif diff --git a/Monika After Story/game/python-packages/winnie32api/common.py b/Monika After Story/game/python-packages/winnie32api/common.py new file mode 100644 index 0000000000..34d3fd79f9 --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/common.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import ctypes +from ctypes.wintypes import ( + INT +) +from dataclasses import dataclass + +from typing import ( + Any, + NamedTuple +) + + +user32 = ctypes.windll.user32 +kernel32 = ctypes.windll.kernel32 + + +HWND = int + +class Winnie32APIError(Exception): pass +class WinAPIError(Winnie32APIError): + """ + Represents an error in win API + """ + def __init__(self, msg: str, code: int): + self.msg = msg + self.code = code + + def __str__(self) -> str: + return f"{self.msg}. Status code: {self.code}" + + +Coord = int + +class Point(NamedTuple): + """ + Represents a point on a screen + """ + x: Coord + y: Coord + +class Rect(NamedTuple): + """ + Represents a rectangle on a screen + """ + top_left: Point + bottom_right: Point + + @classmethod + def from_coords(cls, top: Coord, left: Coord, bottom: Coord, right: Coord) -> Rect: + """ + Constructs a rect from 4 coordinates + """ + return cls( + Point(top, left), + Point(bottom, right) + ) + + +@dataclass +class Pack(): + """ + Class that we use as a pointer to the inner value + """ + value: Any + + +def _reset_last_err(): + """ + Clears the last error + """ + kernel32.SetLastError(INT(0)) + +def _get_last_err() -> int: + """ + Returns the last error code + """ + return kernel32.GetLastError() diff --git a/Monika After Story/game/python-packages/winnie32api/mouse.py b/Monika After Story/game/python-packages/winnie32api/mouse.py new file mode 100644 index 0000000000..44cfb5b496 --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/mouse.py @@ -0,0 +1,23 @@ +import ctypes +import ctypes.wintypes as wt + +from .common import Point, WinAPIError, _get_last_err + + +user32 = ctypes.windll.user32 + + +user32.GetCursorPos.argtypes = (wt.LPPOINT,) +user32.GetCursorPos.restype = wt.BOOL + + +def get_screen_mouse_pos() -> Point: + """ + Returns mouse position in screen coords + """ + c_point = wt.POINT() + result = user32.GetCursorPos(ctypes.byref(c_point)) + if not result: + raise WinAPIError("failed to get mouse position", _get_last_err()) + + return Point(c_point.x, c_point.y) diff --git a/Monika After Story/game/python-packages/winnie32api/notifs.py b/Monika After Story/game/python-packages/winnie32api/notifs.py new file mode 100644 index 0000000000..e337ec9edb --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/notifs.py @@ -0,0 +1,527 @@ +import ctypes +import ctypes.wintypes as wt +from collections import deque +from typing import Optional + +from .common import WinAPIError, Winnie32APIError, _get_last_err + + +user32 = ctypes.windll.user32 +kernel32 = ctypes.windll.kernel32 +shell32 = ctypes.windll.shell32 + + +LRESULT = wt.LPARAM#ctypes.c_long +WNDPROC = ctypes.WINFUNCTYPE(LRESULT, wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) + +CW_USEDEFAULT = -2147483648 +WM_USER = 0x0400 +HWND_MESSAGE = -3 +APP_ID = 922 + +NOTIFS_LIMIT = 100 + + +class NotifyIconDataW(ctypes.Structure): + """ + Docs: https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataw#syntax + """ + _fields_ = [ + ("cbSize", wt.DWORD), + ("hWnd", wt.HWND), + ("uID", wt.UINT), + ("uFlags", wt.UINT), + ("uCallbackMessage", wt.UINT), + ("hIcon", wt.HICON), + ("szTip", wt.WCHAR * 128), + ("dwState", wt.DWORD), + ("dwStateMask", wt.DWORD), + ("szInfo", wt.WCHAR * 256), + ("uVersion", wt.UINT), + ("szInfoTitle", wt.WCHAR * 64), + ("dwInfoFlags", wt.DWORD), + ("guidItem", ctypes.c_char * 16), + ("hBalloonIcon", wt.HICON) + ] + +class WndClassExw(ctypes.Structure): + """ + Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw#syntax + """ + _fields_ = [ + ("cbSize", wt.UINT), + ("style", wt.UINT), + ("lpfnWndProc", WNDPROC), + ("cbClsExtra", wt.INT), + ("cbWndExtra", wt.INT), + ("hInstance", wt.HINSTANCE), + ("hIcon", wt.HICON), + ("hCursor", wt.HANDLE), + ("hbrBackground", wt.HBRUSH), + ("lpszMenuName", wt.LPCWSTR), + ("lpszClassName", wt.LPCWSTR), + ("hIconSm", wt.HICON), + ] + + +class NIF(): + """ + 0x00000001. The uCallbackMessage member is valid. + 0x00000002. The hIcon member is valid. + 0x00000004. The szTip member is valid. + 0x00000008. The dwState and dwStateMask members are valid. + 0x00000010. Display a balloon notification. + The szInfo, szInfoTitle, dwInfoFlags, and uTimeout members are valid. + Note that uTimeout is valid only in Windows 2000 and Windows XP. + To display the balloon notification, specify NIF_INFO and provide text in szInfo. + To remove a balloon notification, specify NIF_INFO and provide an empty + string through szInfo. + To add a notification area icon without displaying a notification, + do not set the NIF_INFO flag. + 0x00000020. + Windows 7 and later: The guidItem is valid. + Windows Vista and earlier: Reserved. + 0x00000040. Windows Vista and later. + If the balloon notification cannot be displayed immediately, discard it. + 0x00000080. Windows Vista and later. Use the standard tooltip. + """ + MESSAGE = 0x00000001 + ICON = 0x00000002 + TIP = 0x00000004 + STATE = 0x00000008 + INFO = 0x00000010 + GUID = 0x00000020 + REALTIME = 0x00000040 + SHOWTIP = 0x00000080 + +class NIS(): + """ + The state of the icon. One or both of the following values + 0x00000001. The icon is hidden. + 0x00000002. The icon resource is shared between multiple icons. + """ + HIDDEN = 0x00000001 + SHAREDICON = 0x00000002 + +class NIIF(): + """ + 0x00000000. No icon. + 0x00000001. An information icon. + 0x00000002. A warning icon. + 0x00000003. An error icon. + 0x00000004. Windows XP SP2 and later. + Windows XP: Use the icon identified in hIcon + as the notification balloon's title icon. + Windows Vista and later: Use the icon identified in hBalloonIcon + as the notification balloon's title icon. + 0x00000010. Windows XP and later. + Do not play the associated sound. Applies only to notifications. + 0x00000020. Windows Vista and later. + The large version of the icon should be used as the notification icon. + 0x00000080. Windows 7 and later. + Do not display the balloon notification if the current user is in "quiet time" + 0x0000000F. Windows XP and later. Reserved. + """ + NONE = 0x00000000 + INFO = 0x00000001 + WARNING = 0x00000002 + ERROR = 0x00000003 + USER = 0x00000004 + NOSOUND = 0x00000010 + LARGE_ICON = 0x00000020 + RESPECT_QUIET_TIME = 0x00000080 + ICON_MASK = 0x0000000F + +class NIM(): + """ + 0x00000000. Adds an icon to the status area. + 0x00000001. Modifies an icon in the status area. + 0x00000002. Deletes an icon from the status area. + 0x00000003. Shell32.dll version 5.0 and later only. + Returns focus to the taskbar notification area. + 0x00000004. Shell32.dll version 5.0 and later only. + Instructs the notification area to behave according to the version number + specified in the uVersion member of the structure pointed to by lpdata. + """ + ADD = 0x00000000 + MODIFY = 0x00000001 + DELETE = 0x00000002 + SETFOCUS = 0x00000003 + SETVERSION = 0x00000004 + + +class LR(): + """ + 0x00002000. When the uType parameter specifies IMAGE_BITMAP, + causes the function to return a DIB section bitmap rather than a compatible bitmap. + This flag is useful for loading a bitmap without mapping it + to the colors of the display device. + 0x00000000. The default flag; it does nothing. All it means is "not LR_MONOCHROME". + 0x00000040. Uses the width or height specified by the system metric values + for cursors or icons, if the cxDesired or cyDesired values are set to zero. + 0x00000010. Loads the stand-alone image from the file specified by lpszName + (icon, cursor, or bitmap file). + 0x00001000. Searches the color table for the image and replaces + the following shades of gray with the corresponding 3-D color. + Dk Gray, RGB(128,128,128) with COLOR_3DSHADOW + Gray, RGB(192,192,192) with COLOR_3DFACE + Lt Gray, RGB(223,223,223) with COLOR_3DLIGHT + Do not use this option if you are loading a bitmap with a color depth greater than 8bpp. + 0x00000020. Retrieves the color value of the first pixel in the image + and replaces the corresponding entry in the color table with + the default window color (COLOR_WINDOW). + 0x00000001. Loads the image in black and white. + 0x00008000. Shares the image handle if the image is loaded multiple times. + 0x00000080. Uses true VGA colors. + """ + CREATEDIBSECTION = 0x00002000 + DEFAULTCOLOR = 0x00000000 + DEFAULTSIZE = 0x00000040 + LOADFROMFILE = 0x00000010 + LOADMAP3DCOLORS = 0x00001000 + LOADTRANSPARENT = 0x00000020 + MONOCHROME = 0x00000001 + SHARED = 0x00008000 + VGACOLOR = 0x00000080 + +class WS(): + """ + Docs: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles + """ + BORDER = 0x00800000 + CAPTION = 0x00C00000 + CHILD = 0x40000000 + CHILDWINDOW = 0x40000000 + CLIPCHILDREN = 0x02000000 + CLIPSIBLINGS = 0x04000000 + DISABLED = 0x08000000 + DLGFRAME = 0x00400000 + GROUP = 0x00020000 + HSCROLL = 0x00100000 + ICONIC = 0x20000000 + MAXIMIZE = 0x01000000 + MAXIMIZEBOX = 0x00010000 + MINIMIZE = 0x20000000 + MINIMIZEBOX = 0x00020000 + OVERLAPPED = 0x00000000 + SYSMENU = 0x00080000 + THICKFRAME = 0x00040000 + OVERLAPPEDWINDOW = ( + OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX + ) + POPUP = 0x80000000 + POPUPWINDOW = POPUP | BORDER | SYSMENU + SIZEBOX = 0x00040000 + TABSTOP = 0x00010000 + TILED = 0x00000000 + TILEDWINDOW = ( + OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX + ) + VISIBLE = 0x10000000 + VSCROLL = 0x00200000 + +class IMAGE(): + """ + 0. Copies a bitmap. + 2. Copies a cursor. + 1. Copies an icon. + """ + BITMAP = wt.UINT(0) + CURSOR = wt.UINT(2) + ICON = wt.UINT(1) + + +user32.LoadImageW.argtypes = ( + wt.HINSTANCE, wt.LPCWSTR, wt.UINT, wt.INT, wt.INT, wt.UINT +) +user32.LoadImageW.restype = wt.HANDLE + +user32.DestroyIcon.argtypes = (wt.HICON,) +user32.DestroyIcon.restype = wt.BOOL + +kernel32.GetModuleHandleW.argtypes = (wt.LPCWSTR,) +kernel32.GetModuleHandleW.restype = wt.HMODULE + +user32.DefWindowProcW.argtypes = (wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) +user32.DefWindowProcW.restype = LRESULT + +user32.RegisterClassExW.argtypes = (ctypes.POINTER(WndClassExw),) +user32.RegisterClassExW.restype = wt.ATOM + +user32.UnregisterClassW.argtypes = (wt.LPCWSTR, wt.HINSTANCE) +user32.UnregisterClassW.restype = wt.BOOL + +user32.CreateWindowExW.argtypes = ( + wt.DWORD, + wt.ATOM,# This could be LPCWSTR instead of ATOM, but we'd have to use cls name + wt.LPCWSTR, wt.DWORD, + wt.INT, wt.INT, wt.INT, wt.INT, + wt.HWND, wt.HMENU, wt.HINSTANCE, wt.LPVOID +) +user32.CreateWindowExW.restype = wt.HWND + +user32.UpdateWindow.argtypes = (wt.HWND,) +user32.UpdateWindow.restype = wt.BOOL + +user32.DestroyWindow.argtypes = (wt.HWND,) +user32.DestroyWindow.restype = wt.BOOL + +shell32.Shell_NotifyIconW.argtypes = (wt.DWORD, ctypes.POINTER(NotifyIconDataW)) +shell32.Shell_NotifyIconW.restype = wt.BOOL + + +class MaxNotifsReachedError(Winnie32APIError): + """ + An error raisedwhen spawned too many WindowsNotif + """ + def __str__(self) -> str: + return "too many notification" + +class WindowsNotif(): + """ + Class reprensets a windows notification + """ + _NOTIF_ID_POOL = deque( + map(str, range(NOTIFS_LIMIT)), + maxlen=NOTIFS_LIMIT + ) + + def __init__( + self, + app_name: str, + icon_path: Optional[str], + title: str, + body: str + ): + """ + Constructs a new windows notification + + IN: + app_name - the name of the app + icon_path - path to optional icon for this notif + title - the notif title + body - the notif body + """ + # Predefine in case we crash + self._hinstance = None + self._win_cls = None + self._cls_atom = None + self._hicon = None + self._hwnd = None + self._nid = None + self._notif_id = None + + if not self._NOTIF_ID_POOL: + raise MaxNotifsReachedError() + + self._app_name = app_name + self._icon_path = icon_path + self._title = title + self._body = body + + self._used = False + self._notif_id = self._NOTIF_ID_POOL.popleft() + + self._after_init() + + def _after_init(self): + self._set_hinstance() + self._register_win_cls() + self._load_icon() + self._create_win() + + def _deinit(self): + self._hide_notif() + self._destroy_win() + self._unload_icon() + self._unregister_win_cls() + + def __del__(self): + self._deinit() + if self._notif_id is not None: + self._NOTIF_ID_POOL.append(self._notif_id) + self._notif_id = None + + def __call__(self): + if not self._used: + self._used = True + self._display_notif() + + def send(self): + """ + Sends this notif, once + """ + self() + + def reset(self): + """ + Resets this notifs allowing it to be send again + """ + self._deinit() + self._after_init() + self._used = False + + def _load_icon(self): + """ + Loads the notification icon + """ + if self._icon_path: + icon_flags = LR.LOADFROMFILE | LR.DEFAULTSIZE + hicon = user32.LoadImageW( + None,# Use NULL since we're loading a "stand-alone" resource + self._icon_path, + IMAGE.ICON, + 0, + 0, + icon_flags + ) + + else: + hicon = 0 + + self._hicon = hicon + + def _unload_icon(self): + """ + Unloads the notification icon + """ + if self._hicon: + user32.DestroyIcon(self._hicon) + + def _set_hinstance(self): + """ + Gets the handler of this dll + """ + self._hinstance = handle = kernel32.GetModuleHandleW(None) + if not handle: + raise WinAPIError("failed to get module handle", _get_last_err()) + + def _register_win_cls(self): + """ + Registers a window class + """ + def winproc(hwnd: wt.HWND, msg: wt.UINT, wparam: wt.WPARAM, lparam: wt.LPARAM) -> LRESULT: + return user32.DefWindowProcW(hwnd, msg, wparam, lparam) + + self._win_cls = win_cls = WndClassExw() + win_cls.cbSize = ctypes.sizeof(win_cls) + win_cls.style = 0 + win_cls.lpfnWndProc = WNDPROC(winproc) + win_cls.cbClsExtra = 0 + win_cls.cbWndExtra = 0 + win_cls.hInstance = self._hinstance + win_cls.hIcon = 0 + win_cls.hCursor = 0 + win_cls.hbrBackground = 0 + win_cls.lpszClassName = self._app_name + self._notif_id + + self._cls_atom = cls_atom = user32.RegisterClassExW(ctypes.byref(win_cls)) + if not cls_atom: + raise WinAPIError("failed to create class ATOM", _get_last_err()) + + def _unregister_win_cls(self): + """ + Unregisters a window class + """ + if self._win_cls: + user32.UnregisterClassW(self._win_cls.lpszClassName, self._hinstance) + + def _create_win(self): + """ + Creates a notification window + """ + win_style = WS.OVERLAPPED | WS.SYSMENU + hwnd = user32.CreateWindowExW( + 0, + self._cls_atom, + # self._win_cls.lpszClassName, + self._app_name, + # self._win_cls.lpszClassName, + win_style, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + None, + None, + self._hinstance, + None + ) + if not hwnd: + raise WinAPIError("failed to create a window", _get_last_err()) + user32.UpdateWindow(hwnd) + self._hwnd = hwnd + + def _destroy_win(self): + """ + Destroys the notification window + """ + if self._hwnd: + user32.DestroyWindow(self._hwnd) + + def _display_notif(self): + """ + Displays this notification + """ + self._nid = nid = NotifyIconDataW() + nid.cbSize = ctypes.sizeof(nid) + nid.hWnd = self._hwnd + nid.uID = APP_ID + nid.uFlags = ( + NIF.ICON | NIF.INFO | NIF.STATE | NIF.MESSAGE | NIF.TIP | NIF.SHOWTIP + ) + nid.uCallbackMessage = WM_USER + 2 + nid.hIcon = self._hicon + nid.szTip = self._app_name[:128] + nid.szInfo = self._body[:256] + nid.uVersion = 4 + nid.szInfoTitle = self._title[:64] + nid.dwInfoFlags = NIIF.NOSOUND | NIIF.USER | NIIF.LARGE_ICON | NIIF.RESPECT_QUIET_TIME + + shell32.Shell_NotifyIconW(NIM.ADD, ctypes.byref(nid)) + shell32.Shell_NotifyIconW(NIM.SETVERSION, ctypes.byref(nid)) + + def _hide_notif(self): + """ + Hides this notification + """ + if self._nid: + shell32.Shell_NotifyIconW(NIM.DELETE, ctypes.byref(self._nid)) + user32.UpdateWindow(self._hwnd) + +class WindowsNotifManager(): + """ + Notification manager + """ + def __init__(self, app_name: str, icon_path: Optional[str]): + """ + Constructor + + IN: + app_name - the app name shared by the notifs + icon_path - the path to the icon shared by the notifs + """ + self._app_name = app_name + self._icon_path = icon_path + + def spawn(self, title: str, body: str) -> WindowsNotif: + """ + Spawns a notif, but doesn't send it + + IN: + title - the title of the notification + body - the body of the notification + """ + return WindowsNotif(self._app_name, self._icon_path, title, body) + + def send(self, title: str, body: str) -> WindowsNotif: + """ + Spawns and sends a notif + + IN: + title - the title of the notification + body - the body of the notification + """ + notif = self.spawn(title, body) + notif.send() + return notif diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py new file mode 100644 index 0000000000..6af8acc19a --- /dev/null +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -0,0 +1,129 @@ +import ctypes +import ctypes.wintypes as wt + +from typing import ( + Optional +) + +from .common import ( + HWND, + Rect, + Pack, + WinAPIError, + _get_last_err, + _reset_last_err +) + + +user32 = ctypes.windll.user32 +kernel32 = ctypes.windll.kernel32 + + +WNDENUMPROC = ctypes.WINFUNCTYPE(wt.BOOL, wt.HWND, wt.LPARAM) + + +user32.IsWindowVisible.argtypes = (wt.HWND,) +user32.IsWindowVisible.restype = wt.BOOL + +user32.GetWindowTextLengthW.argtypes = (wt.HWND,) +user32.GetWindowTextLengthW.restype = wt.INT + +user32.GetWindowTextW.argtypes = (wt.HWND, wt.LPWSTR, wt.INT) +user32.GetWindowTextW.restype = wt.INT + +user32.EnumWindows.argtypes = (WNDENUMPROC, wt.LPARAM) +user32.EnumWindows.restype = wt.BOOL + +user32.GetWindowRect.argtypes = (wt.HWND, wt.LPRECT) +user32.GetWindowRect.restype = wt.BOOL + +user32.GetForegroundWindow.argtypes = () +user32.GetForegroundWindow.restype = wt.HWND + + +def get_hwnd_by_title(title: str) -> Optional[HWND]: + """ + Returns first window hwnd with the given title + """ + pack = Pack(None) + + def callback(hwnd: int, lparam: int) -> bool: + c_hwnd = wt.HWND(hwnd) + + if user32.IsWindowVisible(c_hwnd): + rv = get_window_title(hwnd) + if title == rv: + pack.value = hwnd + return False + + return True + + user32.EnumWindows(WNDENUMPROC(callback), wt.LPARAM(0)) + return pack.value + +def get_window_title(hwnd: HWND) -> str: + """ + Returns a window title as a str + """ + _reset_last_err() + + title_len = user32.GetWindowTextLengthW(hwnd) + if not title_len: + last_err = _get_last_err() + if last_err: + raise WinAPIError("failed to get title length", last_err) + + buffer = ctypes.create_unicode_buffer(title_len + 1) + result = user32.GetWindowTextW( + hwnd, + buffer, + title_len + 1 + ) + if result != title_len: + last_err = _get_last_err() + if last_err: + raise WinAPIError("failed to get title", last_err) + + return buffer.value + +def get_window_rect(hwnd: HWND) -> Rect: + """ + Returns a window rect + """ + c_rect = wt.RECT() + result = user32.GetWindowRect(hwnd, ctypes.byref(c_rect)) + if not result: + raise WinAPIError("failed to get window rect", _get_last_err()) + + return Rect.from_coords(c_rect.top, c_rect.left, c_rect.bottom, c_rect.right) + + +def get_active_window_hwnd() -> Optional[HWND]: + """ + Returns active window title hwnd (id) + """ + active_win_hwnd = user32.GetForegroundWindow() + if not active_win_hwnd or not user32.IsWindowVisible(active_win_hwnd): + return None + + return active_win_hwnd + +def get_active_window_title() -> Optional[str]: + """ + Returns active window title as a str + """ + hwnd = get_active_window_hwnd() + if hwnd is None: + return None + + return get_window_title(hwnd) + +def get_active_window_rect() -> Optional[Rect]: + """ + Returns active window rect + """ + hwnd = get_active_window_hwnd() + if hwnd is None: + return None + + return get_window_rect(hwnd) From d0361b553bdb121060dfccb4bc62c071a12664ea Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:01:47 +0300 Subject: [PATCH 129/180] use compat pickle --- Monika After Story/game/dev/dev_db.rpy | 2 +- Monika After Story/game/import_ddlc.rpy | 3 +-- Monika After Story/game/zz_backup.rpy | 19 ++++++++----------- Monika After Story/game/zz_dockingstation.rpy | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Monika After Story/game/dev/dev_db.rpy b/Monika After Story/game/dev/dev_db.rpy index 904113745a..9a214872fe 100644 --- a/Monika After Story/game/dev/dev_db.rpy +++ b/Monika After Story/game/dev/dev_db.rpy @@ -215,7 +215,7 @@ init python: init python in dev_mas_shared: - import pickle + import renpy.compat.pickle as pickle import codecs import store import store.mas_ev_data_ver as ver diff --git a/Monika After Story/game/import_ddlc.rpy b/Monika After Story/game/import_ddlc.rpy index 1b1cf9d468..8855b75c8d 100644 --- a/Monika After Story/game/import_ddlc.rpy +++ b/Monika After Story/game/import_ddlc.rpy @@ -103,8 +103,7 @@ label import_ddlc_persistent: #Open the persistent save file at ddlc_save_path ddlc_persistent = None try: - with open(ddlc_save_path, "rb") as ddlc_pfile: - ddlc_persistent = mas_dockstat.pickle.loads(ddlc_pfile.read().decode("zlib")) + ddlc_persistent = store.mas_per_check._load_per_data(ddlc_save_path) except Exception as e: store.mas_utils.mas_log.error("Failed to read/decode DDLC persistent: {0}".format(e)) diff --git a/Monika After Story/game/zz_backup.rpy b/Monika After Story/game/zz_backup.rpy index 95fb638227..bb7736ad5d 100644 --- a/Monika After Story/game/zz_backup.rpy +++ b/Monika After Story/game/zz_backup.rpy @@ -19,7 +19,7 @@ default persistent._mas_incompat_per_entered = False python early in mas_per_check: import __main__ - import pickle + import renpy.compat.pickle as pickle import codecs import os import datetime @@ -90,6 +90,12 @@ python early in mas_per_check: store.persistent._mas_incompat_per_rpy_files_found = False + def _load_per_data(path: str): + with open(path, "rb") as per_file: + pickle_data = codecs.decode(per_file.read(), "zlib") + return pickle.loads(pickle_data) + + def tryper(_tp_persistent, get_data=False): """ Tries to read a persistent. @@ -105,13 +111,8 @@ python early in mas_per_check: [1] - the version number, or the persistent data if get_data is True """ - per_file = None try: - per_file = open(_tp_persistent, "rb") - per_data = codecs.decode(per_file.read(), "zlib") - per_file.close() - actual_data = pickle.loads(per_data) - + actual_data = _load_per_data(_tp_persistent) if get_data: return True, actual_data @@ -120,10 +121,6 @@ python early in mas_per_check: except Exception as e: raise e - finally: - if per_file is not None: - per_file.close() - def is_version_compatible(per_version, cur_version): """ diff --git a/Monika After Story/game/zz_dockingstation.rpy b/Monika After Story/game/zz_dockingstation.rpy index 61adead460..4cfb95a630 100644 --- a/Monika After Story/game/zz_dockingstation.rpy +++ b/Monika After Story/game/zz_dockingstation.rpy @@ -1178,7 +1178,7 @@ init -11 python in mas_dockstat: init python in mas_dockstat: import store - import pickle + import renpy.compat.pickle as pickle # previous vars dict previous_vars = dict() From 3c3300cc9ef1c3d6e9eb464119ff1406bea7e2a8 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:05:43 +0300 Subject: [PATCH 130/180] because ignoring my input was a smart idea --- Monika After Story/game/0config.rpy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index 31c525025d..7df6505fa8 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -123,3 +123,5 @@ define config.main_menu_music = audio.t1 define config.window_show_transition = dissolve_textbox define config.window_hide_transition = dissolve_textbox + +define config.mouse_focus_clickthrough = True From 5e19330caf92f2832b14c3336afd008553e57dd3 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:30:47 +0300 Subject: [PATCH 131/180] log why clothes/hair has been restored --- Monika After Story/game/sprite-chart.rpy | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/sprite-chart.rpy b/Monika After Story/game/sprite-chart.rpy index 8fabeb6345..bb7cd18c54 100644 --- a/Monika After Story/game/sprite-chart.rpy +++ b/Monika After Story/game/sprite-chart.rpy @@ -2803,10 +2803,18 @@ init -3 python: startup - True if we are loading on start, False if not (Default: False) """ + clothes = store.mas_sprites.CLOTH_MAP.get(_clothes_name, None) + if clothes is None: + store.mas_utils.mas_log.warning(f"Failed to find clothes '{_clothes_name}', restoring default") + clothes = store.mas_clothes_def + hair = store.mas_sprites.HAIR_MAP.get(_hair_name, None) + if hair is None: + store.mas_utils.mas_log.warning(f"Failed to find hair '{_hair_name}', restoring default") + hair = store.mas_hair_def # clothes and hair self.change_outfit( - store.mas_sprites.CLOTH_MAP.get(_clothes_name, store.mas_clothes_def), - store.mas_sprites.HAIR_MAP.get(_hair_name, store.mas_hair_def), + clothes, + hair, startup=startup ) From fc45a338a8b7f341e843bcd9a27b644add239068 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:34:32 +0300 Subject: [PATCH 132/180] remove tests to save some space --- .../python-packages/unittest/test/__init__.py | 22 - .../python-packages/unittest/test/__main__.py | 18 - .../unittest/test/_test_warnings.py | 73 - .../python-packages/unittest/test/dummy.py | 1 - .../python-packages/unittest/test/support.py | 138 -- .../unittest/test/test_assertions.py | 413 ---- .../unittest/test/test_async_case.py | 222 -- .../unittest/test/test_break.py | 280 --- .../unittest/test/test_case.py | 1852 -------------- .../unittest/test/test_discovery.py | 879 ------- .../unittest/test/test_functiontestcase.py | 148 -- .../unittest/test/test_loader.py | 1595 ------------ .../unittest/test/test_program.py | 440 ---- .../unittest/test/test_result.py | 704 ------ .../unittest/test/test_runner.py | 1013 -------- .../unittest/test/test_setups.py | 507 ---- .../unittest/test/test_skipping.py | 271 -- .../unittest/test/test_suite.py | 447 ---- .../unittest/test/testmock/__init__.py | 17 - .../unittest/test/testmock/__main__.py | 18 - .../unittest/test/testmock/support.py | 16 - .../unittest/test/testmock/testasync.py | 1059 -------- .../unittest/test/testmock/testcallable.py | 150 -- .../unittest/test/testmock/testhelpers.py | 1127 --------- .../test/testmock/testmagicmethods.py | 509 ---- .../unittest/test/testmock/testmock.py | 2171 ----------------- .../unittest/test/testmock/testpatch.py | 1947 --------------- .../unittest/test/testmock/testsealable.py | 176 -- .../unittest/test/testmock/testsentinel.py | 41 - .../unittest/test/testmock/testwith.py | 347 --- 30 files changed, 16601 deletions(-) delete mode 100644 Monika After Story/game/python-packages/unittest/test/__init__.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/__main__.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/_test_warnings.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/dummy.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/support.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_assertions.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_async_case.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_break.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_case.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_discovery.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_loader.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_program.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_result.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_runner.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_setups.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_skipping.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/test_suite.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/__init__.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/__main__.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/support.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testasync.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testmock.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py delete mode 100644 Monika After Story/game/python-packages/unittest/test/testmock/testwith.py diff --git a/Monika After Story/game/python-packages/unittest/test/__init__.py b/Monika After Story/game/python-packages/unittest/test/__init__.py deleted file mode 100644 index cdae8a7442..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import sys -import unittest - - -here = os.path.dirname(__file__) -loader = unittest.defaultTestLoader - -def suite(): - suite = unittest.TestSuite() - for fn in os.listdir(here): - if fn.startswith("test") and fn.endswith(".py"): - modname = "unittest.test." + fn[:-3] - __import__(modname) - module = sys.modules[modname] - suite.addTest(loader.loadTestsFromModule(module)) - suite.addTest(loader.loadTestsFromName('unittest.test.testmock')) - return suite - - -if __name__ == "__main__": - unittest.main(defaultTest="suite") diff --git a/Monika After Story/game/python-packages/unittest/test/__main__.py b/Monika After Story/game/python-packages/unittest/test/__main__.py deleted file mode 100644 index 44d0591e84..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/__main__.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import unittest - - -def load_tests(loader, standard_tests, pattern): - # top level directory cached on loader instance - this_dir = os.path.dirname(__file__) - pattern = pattern or "test_*.py" - # We are inside unittest.test, so the top-level is two notches up - top_level_dir = os.path.dirname(os.path.dirname(this_dir)) - package_tests = loader.discover(start_dir=this_dir, pattern=pattern, - top_level_dir=top_level_dir) - standard_tests.addTests(package_tests) - return standard_tests - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/_test_warnings.py b/Monika After Story/game/python-packages/unittest/test/_test_warnings.py deleted file mode 100644 index 5cbfb532ad..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/_test_warnings.py +++ /dev/null @@ -1,73 +0,0 @@ -# helper module for test_runner.Test_TextTestRunner.test_warnings - -""" -This module has a number of tests that raise different kinds of warnings. -When the tests are run, the warnings are caught and their messages are printed -to stdout. This module also accepts an arg that is then passed to -unittest.main to affect the behavior of warnings. -Test_TextTestRunner.test_warnings executes this script with different -combinations of warnings args and -W flags and check that the output is correct. -See #10535. -""" - -import sys -import unittest -import warnings - -def warnfun(): - warnings.warn('rw', RuntimeWarning) - -class TestWarnings(unittest.TestCase): - # unittest warnings will be printed at most once per type (max one message - # for the fail* methods, and one for the assert* methods) - def test_assert(self): - self.assertEquals(2+2, 4) - self.assertEquals(2*2, 4) - self.assertEquals(2**2, 4) - - def test_fail(self): - self.failUnless(1) - self.failUnless(True) - - def test_other_unittest(self): - self.assertAlmostEqual(2+2, 4) - self.assertNotAlmostEqual(4+4, 2) - - # these warnings are normally silenced, but they are printed in unittest - def test_deprecation(self): - warnings.warn('dw', DeprecationWarning) - warnings.warn('dw', DeprecationWarning) - warnings.warn('dw', DeprecationWarning) - - def test_import(self): - warnings.warn('iw', ImportWarning) - warnings.warn('iw', ImportWarning) - warnings.warn('iw', ImportWarning) - - # user warnings should always be printed - def test_warning(self): - warnings.warn('uw') - warnings.warn('uw') - warnings.warn('uw') - - # these warnings come from the same place; they will be printed - # only once by default or three times if the 'always' filter is used - def test_function(self): - - warnfun() - warnfun() - warnfun() - - - -if __name__ == '__main__': - with warnings.catch_warnings(record=True) as ws: - # if an arg is provided pass it to unittest.main as 'warnings' - if len(sys.argv) == 2: - unittest.main(exit=False, warnings=sys.argv.pop()) - else: - unittest.main(exit=False) - - # print all the warning messages collected - for w in ws: - print(w.message) diff --git a/Monika After Story/game/python-packages/unittest/test/dummy.py b/Monika After Story/game/python-packages/unittest/test/dummy.py deleted file mode 100644 index e4f14e4035..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/dummy.py +++ /dev/null @@ -1 +0,0 @@ -# Empty module for testing the loading of modules diff --git a/Monika After Story/game/python-packages/unittest/test/support.py b/Monika After Story/game/python-packages/unittest/test/support.py deleted file mode 100644 index 529265304f..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/support.py +++ /dev/null @@ -1,138 +0,0 @@ -import unittest - - -class TestEquality(object): - """Used as a mixin for TestCase""" - - # Check for a valid __eq__ implementation - def test_eq(self): - for obj_1, obj_2 in self.eq_pairs: - self.assertEqual(obj_1, obj_2) - self.assertEqual(obj_2, obj_1) - - # Check for a valid __ne__ implementation - def test_ne(self): - for obj_1, obj_2 in self.ne_pairs: - self.assertNotEqual(obj_1, obj_2) - self.assertNotEqual(obj_2, obj_1) - -class TestHashing(object): - """Used as a mixin for TestCase""" - - # Check for a valid __hash__ implementation - def test_hash(self): - for obj_1, obj_2 in self.eq_pairs: - try: - if not hash(obj_1) == hash(obj_2): - self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) - except Exception as e: - self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) - - for obj_1, obj_2 in self.ne_pairs: - try: - if hash(obj_1) == hash(obj_2): - self.fail("%s and %s hash equal, but shouldn't" % - (obj_1, obj_2)) - except Exception as e: - self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) - - -class _BaseLoggingResult(unittest.TestResult): - def __init__(self, log): - self._events = log - super().__init__() - - def startTest(self, test): - self._events.append('startTest') - super().startTest(test) - - def startTestRun(self): - self._events.append('startTestRun') - super().startTestRun() - - def stopTest(self, test): - self._events.append('stopTest') - super().stopTest(test) - - def stopTestRun(self): - self._events.append('stopTestRun') - super().stopTestRun() - - def addFailure(self, *args): - self._events.append('addFailure') - super().addFailure(*args) - - def addSuccess(self, *args): - self._events.append('addSuccess') - super().addSuccess(*args) - - def addError(self, *args): - self._events.append('addError') - super().addError(*args) - - def addSkip(self, *args): - self._events.append('addSkip') - super().addSkip(*args) - - def addExpectedFailure(self, *args): - self._events.append('addExpectedFailure') - super().addExpectedFailure(*args) - - def addUnexpectedSuccess(self, *args): - self._events.append('addUnexpectedSuccess') - super().addUnexpectedSuccess(*args) - - -class LegacyLoggingResult(_BaseLoggingResult): - """ - A legacy TestResult implementation, without an addSubTest method, - which records its method calls. - """ - - @property - def addSubTest(self): - raise AttributeError - - -class LoggingResult(_BaseLoggingResult): - """ - A TestResult implementation which records its method calls. - """ - - def addSubTest(self, test, subtest, err): - if err is None: - self._events.append('addSubTestSuccess') - else: - self._events.append('addSubTestFailure') - super().addSubTest(test, subtest, err) - - -class ResultWithNoStartTestRunStopTestRun(object): - """An object honouring TestResult before startTestRun/stopTestRun.""" - - def __init__(self): - self.failures = [] - self.errors = [] - self.testsRun = 0 - self.skipped = [] - self.expectedFailures = [] - self.unexpectedSuccesses = [] - self.shouldStop = False - - def startTest(self, test): - pass - - def stopTest(self, test): - pass - - def addError(self, test): - pass - - def addFailure(self, test): - pass - - def addSuccess(self, test): - pass - - def wasSuccessful(self): - return True diff --git a/Monika After Story/game/python-packages/unittest/test/test_assertions.py b/Monika After Story/game/python-packages/unittest/test/test_assertions.py deleted file mode 100644 index f5e64d68e7..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_assertions.py +++ /dev/null @@ -1,413 +0,0 @@ -import datetime -import warnings -import weakref -import unittest -from itertools import product - - -class Test_Assertions(unittest.TestCase): - def test_AlmostEqual(self): - self.assertAlmostEqual(1.00000001, 1.0) - self.assertNotAlmostEqual(1.0000001, 1.0) - self.assertRaises(self.failureException, - self.assertAlmostEqual, 1.0000001, 1.0) - self.assertRaises(self.failureException, - self.assertNotAlmostEqual, 1.00000001, 1.0) - - self.assertAlmostEqual(1.1, 1.0, places=0) - self.assertRaises(self.failureException, - self.assertAlmostEqual, 1.1, 1.0, places=1) - - self.assertAlmostEqual(0, .1+.1j, places=0) - self.assertNotAlmostEqual(0, .1+.1j, places=1) - self.assertRaises(self.failureException, - self.assertAlmostEqual, 0, .1+.1j, places=1) - self.assertRaises(self.failureException, - self.assertNotAlmostEqual, 0, .1+.1j, places=0) - - self.assertAlmostEqual(float('inf'), float('inf')) - self.assertRaises(self.failureException, self.assertNotAlmostEqual, - float('inf'), float('inf')) - - def test_AmostEqualWithDelta(self): - self.assertAlmostEqual(1.1, 1.0, delta=0.5) - self.assertAlmostEqual(1.0, 1.1, delta=0.5) - self.assertNotAlmostEqual(1.1, 1.0, delta=0.05) - self.assertNotAlmostEqual(1.0, 1.1, delta=0.05) - - self.assertAlmostEqual(1.0, 1.0, delta=0.5) - self.assertRaises(self.failureException, self.assertNotAlmostEqual, - 1.0, 1.0, delta=0.5) - - self.assertRaises(self.failureException, self.assertAlmostEqual, - 1.1, 1.0, delta=0.05) - self.assertRaises(self.failureException, self.assertNotAlmostEqual, - 1.1, 1.0, delta=0.5) - - self.assertRaises(TypeError, self.assertAlmostEqual, - 1.1, 1.0, places=2, delta=2) - self.assertRaises(TypeError, self.assertNotAlmostEqual, - 1.1, 1.0, places=2, delta=2) - - first = datetime.datetime.now() - second = first + datetime.timedelta(seconds=10) - self.assertAlmostEqual(first, second, - delta=datetime.timedelta(seconds=20)) - self.assertNotAlmostEqual(first, second, - delta=datetime.timedelta(seconds=5)) - - def test_assertRaises(self): - def _raise(e): - raise e - self.assertRaises(KeyError, _raise, KeyError) - self.assertRaises(KeyError, _raise, KeyError("key")) - try: - self.assertRaises(KeyError, lambda: None) - except self.failureException as e: - self.assertIn("KeyError not raised", str(e)) - else: - self.fail("assertRaises() didn't fail") - try: - self.assertRaises(KeyError, _raise, ValueError) - except ValueError: - pass - else: - self.fail("assertRaises() didn't let exception pass through") - with self.assertRaises(KeyError) as cm: - try: - raise KeyError - except Exception as e: - exc = e - raise - self.assertIs(cm.exception, exc) - - with self.assertRaises(KeyError): - raise KeyError("key") - try: - with self.assertRaises(KeyError): - pass - except self.failureException as e: - self.assertIn("KeyError not raised", str(e)) - else: - self.fail("assertRaises() didn't fail") - try: - with self.assertRaises(KeyError): - raise ValueError - except ValueError: - pass - else: - self.fail("assertRaises() didn't let exception pass through") - - def test_assertRaises_frames_survival(self): - # Issue #9815: assertRaises should avoid keeping local variables - # in a traceback alive. - class A: - pass - wr = None - - class Foo(unittest.TestCase): - - def foo(self): - nonlocal wr - a = A() - wr = weakref.ref(a) - try: - raise OSError - except OSError: - raise ValueError - - def test_functional(self): - self.assertRaises(ValueError, self.foo) - - def test_with(self): - with self.assertRaises(ValueError): - self.foo() - - Foo("test_functional").run() - self.assertIsNone(wr()) - Foo("test_with").run() - self.assertIsNone(wr()) - - def testAssertNotRegex(self): - self.assertNotRegex('Ala ma kota', r'r+') - try: - self.assertNotRegex('Ala ma kota', r'k.t', 'Message') - except self.failureException as e: - self.assertIn('Message', e.args[0]) - else: - self.fail('assertNotRegex should have failed.') - - -class TestLongMessage(unittest.TestCase): - """Test that the individual asserts honour longMessage. - This actually tests all the message behaviour for - asserts that use longMessage.""" - - def setUp(self): - class TestableTestFalse(unittest.TestCase): - longMessage = False - failureException = self.failureException - - def testTest(self): - pass - - class TestableTestTrue(unittest.TestCase): - longMessage = True - failureException = self.failureException - - def testTest(self): - pass - - self.testableTrue = TestableTestTrue('testTest') - self.testableFalse = TestableTestFalse('testTest') - - def testDefault(self): - self.assertTrue(unittest.TestCase.longMessage) - - def test_formatMsg(self): - self.assertEqual(self.testableFalse._formatMessage(None, "foo"), "foo") - self.assertEqual(self.testableFalse._formatMessage("foo", "bar"), "foo") - - self.assertEqual(self.testableTrue._formatMessage(None, "foo"), "foo") - self.assertEqual(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") - - # This blows up if _formatMessage uses string concatenation - self.testableTrue._formatMessage(object(), 'foo') - - def test_formatMessage_unicode_error(self): - one = ''.join(chr(i) for i in range(255)) - # this used to cause a UnicodeDecodeError constructing msg - self.testableTrue._formatMessage(one, '\uFFFD') - - def assertMessages(self, methodName, args, errors): - """ - Check that methodName(*args) raises the correct error messages. - errors should be a list of 4 regex that match the error when: - 1) longMessage = False and no msg passed; - 2) longMessage = False and msg passed; - 3) longMessage = True and no msg passed; - 4) longMessage = True and msg passed; - """ - def getMethod(i): - useTestableFalse = i < 2 - if useTestableFalse: - test = self.testableFalse - else: - test = self.testableTrue - return getattr(test, methodName) - - for i, expected_regex in enumerate(errors): - testMethod = getMethod(i) - kwargs = {} - withMsg = i % 2 - if withMsg: - kwargs = {"msg": "oops"} - - with self.assertRaisesRegex(self.failureException, - expected_regex=expected_regex): - testMethod(*args, **kwargs) - - def testAssertTrue(self): - self.assertMessages('assertTrue', (False,), - ["^False is not true$", "^oops$", "^False is not true$", - "^False is not true : oops$"]) - - def testAssertFalse(self): - self.assertMessages('assertFalse', (True,), - ["^True is not false$", "^oops$", "^True is not false$", - "^True is not false : oops$"]) - - def testNotEqual(self): - self.assertMessages('assertNotEqual', (1, 1), - ["^1 == 1$", "^oops$", "^1 == 1$", - "^1 == 1 : oops$"]) - - def testAlmostEqual(self): - self.assertMessages( - 'assertAlmostEqual', (1, 2), - [r"^1 != 2 within 7 places \(1 difference\)$", "^oops$", - r"^1 != 2 within 7 places \(1 difference\)$", - r"^1 != 2 within 7 places \(1 difference\) : oops$"]) - - def testNotAlmostEqual(self): - self.assertMessages('assertNotAlmostEqual', (1, 1), - ["^1 == 1 within 7 places$", "^oops$", - "^1 == 1 within 7 places$", "^1 == 1 within 7 places : oops$"]) - - def test_baseAssertEqual(self): - self.assertMessages('_baseAssertEqual', (1, 2), - ["^1 != 2$", "^oops$", "^1 != 2$", "^1 != 2 : oops$"]) - - def testAssertSequenceEqual(self): - # Error messages are multiline so not testing on full message - # assertTupleEqual and assertListEqual delegate to this method - self.assertMessages('assertSequenceEqual', ([], [None]), - [r"\+ \[None\]$", "^oops$", r"\+ \[None\]$", - r"\+ \[None\] : oops$"]) - - def testAssertSetEqual(self): - self.assertMessages('assertSetEqual', (set(), set([None])), - ["None$", "^oops$", "None$", - "None : oops$"]) - - def testAssertIn(self): - self.assertMessages('assertIn', (None, []), - [r'^None not found in \[\]$', "^oops$", - r'^None not found in \[\]$', - r'^None not found in \[\] : oops$']) - - def testAssertNotIn(self): - self.assertMessages('assertNotIn', (None, [None]), - [r'^None unexpectedly found in \[None\]$', "^oops$", - r'^None unexpectedly found in \[None\]$', - r'^None unexpectedly found in \[None\] : oops$']) - - def testAssertDictEqual(self): - self.assertMessages('assertDictEqual', ({}, {'key': 'value'}), - [r"\+ \{'key': 'value'\}$", "^oops$", - r"\+ \{'key': 'value'\}$", - r"\+ \{'key': 'value'\} : oops$"]) - - def testAssertDictContainsSubset(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - - self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}), - ["^Missing: 'key'$", "^oops$", - "^Missing: 'key'$", - "^Missing: 'key' : oops$"]) - - def testAssertMultiLineEqual(self): - self.assertMessages('assertMultiLineEqual', ("", "foo"), - [r"\+ foo$", "^oops$", - r"\+ foo$", - r"\+ foo : oops$"]) - - def testAssertLess(self): - self.assertMessages('assertLess', (2, 1), - ["^2 not less than 1$", "^oops$", - "^2 not less than 1$", "^2 not less than 1 : oops$"]) - - def testAssertLessEqual(self): - self.assertMessages('assertLessEqual', (2, 1), - ["^2 not less than or equal to 1$", "^oops$", - "^2 not less than or equal to 1$", - "^2 not less than or equal to 1 : oops$"]) - - def testAssertGreater(self): - self.assertMessages('assertGreater', (1, 2), - ["^1 not greater than 2$", "^oops$", - "^1 not greater than 2$", - "^1 not greater than 2 : oops$"]) - - def testAssertGreaterEqual(self): - self.assertMessages('assertGreaterEqual', (1, 2), - ["^1 not greater than or equal to 2$", "^oops$", - "^1 not greater than or equal to 2$", - "^1 not greater than or equal to 2 : oops$"]) - - def testAssertIsNone(self): - self.assertMessages('assertIsNone', ('not None',), - ["^'not None' is not None$", "^oops$", - "^'not None' is not None$", - "^'not None' is not None : oops$"]) - - def testAssertIsNotNone(self): - self.assertMessages('assertIsNotNone', (None,), - ["^unexpectedly None$", "^oops$", - "^unexpectedly None$", - "^unexpectedly None : oops$"]) - - def testAssertIs(self): - self.assertMessages('assertIs', (None, 'foo'), - ["^None is not 'foo'$", "^oops$", - "^None is not 'foo'$", - "^None is not 'foo' : oops$"]) - - def testAssertIsNot(self): - self.assertMessages('assertIsNot', (None, None), - ["^unexpectedly identical: None$", "^oops$", - "^unexpectedly identical: None$", - "^unexpectedly identical: None : oops$"]) - - def testAssertRegex(self): - self.assertMessages('assertRegex', ('foo', 'bar'), - ["^Regex didn't match:", - "^oops$", - "^Regex didn't match:", - "^Regex didn't match: (.*) : oops$"]) - - def testAssertNotRegex(self): - self.assertMessages('assertNotRegex', ('foo', 'foo'), - ["^Regex matched:", - "^oops$", - "^Regex matched:", - "^Regex matched: (.*) : oops$"]) - - - def assertMessagesCM(self, methodName, args, func, errors): - """ - Check that the correct error messages are raised while executing: - with method(*args): - func() - *errors* should be a list of 4 regex that match the error when: - 1) longMessage = False and no msg passed; - 2) longMessage = False and msg passed; - 3) longMessage = True and no msg passed; - 4) longMessage = True and msg passed; - """ - p = product((self.testableFalse, self.testableTrue), - ({}, {"msg": "oops"})) - for (cls, kwargs), err in zip(p, errors): - method = getattr(cls, methodName) - with self.assertRaisesRegex(cls.failureException, err): - with method(*args, **kwargs) as cm: - func() - - def testAssertRaises(self): - self.assertMessagesCM('assertRaises', (TypeError,), lambda: None, - ['^TypeError not raised$', '^oops$', - '^TypeError not raised$', - '^TypeError not raised : oops$']) - - def testAssertRaisesRegex(self): - # test error not raised - self.assertMessagesCM('assertRaisesRegex', (TypeError, 'unused regex'), - lambda: None, - ['^TypeError not raised$', '^oops$', - '^TypeError not raised$', - '^TypeError not raised : oops$']) - # test error raised but with wrong message - def raise_wrong_message(): - raise TypeError('foo') - self.assertMessagesCM('assertRaisesRegex', (TypeError, 'regex'), - raise_wrong_message, - ['^"regex" does not match "foo"$', '^oops$', - '^"regex" does not match "foo"$', - '^"regex" does not match "foo" : oops$']) - - def testAssertWarns(self): - self.assertMessagesCM('assertWarns', (UserWarning,), lambda: None, - ['^UserWarning not triggered$', '^oops$', - '^UserWarning not triggered$', - '^UserWarning not triggered : oops$']) - - def testAssertWarnsRegex(self): - # test error not raised - self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'unused regex'), - lambda: None, - ['^UserWarning not triggered$', '^oops$', - '^UserWarning not triggered$', - '^UserWarning not triggered : oops$']) - # test warning raised but with wrong message - def raise_wrong_message(): - warnings.warn('foo') - self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'regex'), - raise_wrong_message, - ['^"regex" does not match "foo"$', '^oops$', - '^"regex" does not match "foo"$', - '^"regex" does not match "foo" : oops$']) - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_async_case.py b/Monika After Story/game/python-packages/unittest/test/test_async_case.py deleted file mode 100644 index d01864b693..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_async_case.py +++ /dev/null @@ -1,222 +0,0 @@ -import asyncio -import unittest - - -def tearDownModule(): - asyncio.set_event_loop_policy(None) - - -class TestAsyncCase(unittest.TestCase): - def test_full_cycle(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - def setUp(self): - self.assertEqual(events, []) - events.append('setUp') - - async def asyncSetUp(self): - self.assertEqual(events, ['setUp']) - events.append('asyncSetUp') - - async def test_func(self): - self.assertEqual(events, ['setUp', - 'asyncSetUp']) - events.append('test') - self.addAsyncCleanup(self.on_cleanup) - - async def asyncTearDown(self): - self.assertEqual(events, ['setUp', - 'asyncSetUp', - 'test']) - events.append('asyncTearDown') - - def tearDown(self): - self.assertEqual(events, ['setUp', - 'asyncSetUp', - 'test', - 'asyncTearDown']) - events.append('tearDown') - - async def on_cleanup(self): - self.assertEqual(events, ['setUp', - 'asyncSetUp', - 'test', - 'asyncTearDown', - 'tearDown']) - events.append('cleanup') - - test = Test("test_func") - test.run() - self.assertEqual(events, ['setUp', - 'asyncSetUp', - 'test', - 'asyncTearDown', - 'tearDown', - 'cleanup']) - - def test_exception_in_setup(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - events.append('asyncSetUp') - raise Exception() - - async def test_func(self): - events.append('test') - self.addAsyncCleanup(self.on_cleanup) - - async def asyncTearDown(self): - events.append('asyncTearDown') - - async def on_cleanup(self): - events.append('cleanup') - - - test = Test("test_func") - test.run() - self.assertEqual(events, ['asyncSetUp']) - - def test_exception_in_test(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - events.append('asyncSetUp') - - async def test_func(self): - events.append('test') - raise Exception() - self.addAsyncCleanup(self.on_cleanup) - - async def asyncTearDown(self): - events.append('asyncTearDown') - - async def on_cleanup(self): - events.append('cleanup') - - test = Test("test_func") - test.run() - self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown']) - - def test_exception_in_test_after_adding_cleanup(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - events.append('asyncSetUp') - - async def test_func(self): - events.append('test') - self.addAsyncCleanup(self.on_cleanup) - raise Exception() - - async def asyncTearDown(self): - events.append('asyncTearDown') - - async def on_cleanup(self): - events.append('cleanup') - - test = Test("test_func") - test.run() - self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) - - def test_exception_in_tear_down(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - events.append('asyncSetUp') - - async def test_func(self): - events.append('test') - self.addAsyncCleanup(self.on_cleanup) - - async def asyncTearDown(self): - events.append('asyncTearDown') - raise Exception() - - async def on_cleanup(self): - events.append('cleanup') - - test = Test("test_func") - test.run() - self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) - - - def test_exception_in_tear_clean_up(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - events.append('asyncSetUp') - - async def test_func(self): - events.append('test') - self.addAsyncCleanup(self.on_cleanup) - - async def asyncTearDown(self): - events.append('asyncTearDown') - - async def on_cleanup(self): - events.append('cleanup') - raise Exception() - - test = Test("test_func") - test.run() - self.assertEqual(events, ['asyncSetUp', 'test', 'asyncTearDown', 'cleanup']) - - def test_cleanups_interleave_order(self): - events = [] - - class Test(unittest.IsolatedAsyncioTestCase): - async def test_func(self): - self.addAsyncCleanup(self.on_sync_cleanup, 1) - self.addAsyncCleanup(self.on_async_cleanup, 2) - self.addAsyncCleanup(self.on_sync_cleanup, 3) - self.addAsyncCleanup(self.on_async_cleanup, 4) - - async def on_sync_cleanup(self, val): - events.append(f'sync_cleanup {val}') - - async def on_async_cleanup(self, val): - events.append(f'async_cleanup {val}') - - test = Test("test_func") - test.run() - self.assertEqual(events, ['async_cleanup 4', - 'sync_cleanup 3', - 'async_cleanup 2', - 'sync_cleanup 1']) - - def test_base_exception_from_async_method(self): - events = [] - class Test(unittest.IsolatedAsyncioTestCase): - async def test_base(self): - events.append("test_base") - raise BaseException() - events.append("not it") - - async def test_no_err(self): - events.append("test_no_err") - - async def test_cancel(self): - raise asyncio.CancelledError() - - test = Test("test_base") - output = test.run() - self.assertFalse(output.wasSuccessful()) - - test = Test("test_no_err") - test.run() - self.assertEqual(events, ['test_base', 'test_no_err']) - - test = Test("test_cancel") - output = test.run() - self.assertFalse(output.wasSuccessful()) - - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_break.py b/Monika After Story/game/python-packages/unittest/test/test_break.py deleted file mode 100644 index eebd2b610c..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_break.py +++ /dev/null @@ -1,280 +0,0 @@ -import gc -import io -import os -import sys -import signal -import weakref - -import unittest - - -@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") -@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") -class TestBreak(unittest.TestCase): - int_handler = None - - def setUp(self): - self._default_handler = signal.getsignal(signal.SIGINT) - if self.int_handler is not None: - signal.signal(signal.SIGINT, self.int_handler) - - def tearDown(self): - signal.signal(signal.SIGINT, self._default_handler) - unittest.signals._results = weakref.WeakKeyDictionary() - unittest.signals._interrupt_handler = None - - - def testInstallHandler(self): - default_handler = signal.getsignal(signal.SIGINT) - unittest.installHandler() - self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) - - try: - pid = os.getpid() - os.kill(pid, signal.SIGINT) - except KeyboardInterrupt: - self.fail("KeyboardInterrupt not handled") - - self.assertTrue(unittest.signals._interrupt_handler.called) - - def testRegisterResult(self): - result = unittest.TestResult() - self.assertNotIn(result, unittest.signals._results) - - unittest.registerResult(result) - try: - self.assertIn(result, unittest.signals._results) - finally: - unittest.removeResult(result) - - def testInterruptCaught(self): - default_handler = signal.getsignal(signal.SIGINT) - - result = unittest.TestResult() - unittest.installHandler() - unittest.registerResult(result) - - self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) - - def test(result): - pid = os.getpid() - os.kill(pid, signal.SIGINT) - result.breakCaught = True - self.assertTrue(result.shouldStop) - - try: - test(result) - except KeyboardInterrupt: - self.fail("KeyboardInterrupt not handled") - self.assertTrue(result.breakCaught) - - - def testSecondInterrupt(self): - # Can't use skipIf decorator because the signal handler may have - # been changed after defining this method. - if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: - self.skipTest("test requires SIGINT to not be ignored") - result = unittest.TestResult() - unittest.installHandler() - unittest.registerResult(result) - - def test(result): - pid = os.getpid() - os.kill(pid, signal.SIGINT) - result.breakCaught = True - self.assertTrue(result.shouldStop) - os.kill(pid, signal.SIGINT) - self.fail("Second KeyboardInterrupt not raised") - - try: - test(result) - except KeyboardInterrupt: - pass - else: - self.fail("Second KeyboardInterrupt not raised") - self.assertTrue(result.breakCaught) - - - def testTwoResults(self): - unittest.installHandler() - - result = unittest.TestResult() - unittest.registerResult(result) - new_handler = signal.getsignal(signal.SIGINT) - - result2 = unittest.TestResult() - unittest.registerResult(result2) - self.assertEqual(signal.getsignal(signal.SIGINT), new_handler) - - result3 = unittest.TestResult() - - def test(result): - pid = os.getpid() - os.kill(pid, signal.SIGINT) - - try: - test(result) - except KeyboardInterrupt: - self.fail("KeyboardInterrupt not handled") - - self.assertTrue(result.shouldStop) - self.assertTrue(result2.shouldStop) - self.assertFalse(result3.shouldStop) - - - def testHandlerReplacedButCalled(self): - # Can't use skipIf decorator because the signal handler may have - # been changed after defining this method. - if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: - self.skipTest("test requires SIGINT to not be ignored") - # If our handler has been replaced (is no longer installed) but is - # called by the *new* handler, then it isn't safe to delay the - # SIGINT and we should immediately delegate to the default handler - unittest.installHandler() - - handler = signal.getsignal(signal.SIGINT) - def new_handler(frame, signum): - handler(frame, signum) - signal.signal(signal.SIGINT, new_handler) - - try: - pid = os.getpid() - os.kill(pid, signal.SIGINT) - except KeyboardInterrupt: - pass - else: - self.fail("replaced but delegated handler doesn't raise interrupt") - - def testRunner(self): - # Creating a TextTestRunner with the appropriate argument should - # register the TextTestResult it creates - runner = unittest.TextTestRunner(stream=io.StringIO()) - - result = runner.run(unittest.TestSuite()) - self.assertIn(result, unittest.signals._results) - - def testWeakReferences(self): - # Calling registerResult on a result should not keep it alive - result = unittest.TestResult() - unittest.registerResult(result) - - ref = weakref.ref(result) - del result - - # For non-reference counting implementations - gc.collect();gc.collect() - self.assertIsNone(ref()) - - - def testRemoveResult(self): - result = unittest.TestResult() - unittest.registerResult(result) - - unittest.installHandler() - self.assertTrue(unittest.removeResult(result)) - - # Should this raise an error instead? - self.assertFalse(unittest.removeResult(unittest.TestResult())) - - try: - pid = os.getpid() - os.kill(pid, signal.SIGINT) - except KeyboardInterrupt: - pass - - self.assertFalse(result.shouldStop) - - def testMainInstallsHandler(self): - failfast = object() - test = object() - verbosity = object() - result = object() - default_handler = signal.getsignal(signal.SIGINT) - - class FakeRunner(object): - initArgs = [] - runArgs = [] - def __init__(self, *args, **kwargs): - self.initArgs.append((args, kwargs)) - def run(self, test): - self.runArgs.append(test) - return result - - class Program(unittest.TestProgram): - def __init__(self, catchbreak): - self.exit = False - self.verbosity = verbosity - self.failfast = failfast - self.catchbreak = catchbreak - self.tb_locals = False - self.testRunner = FakeRunner - self.test = test - self.result = None - - p = Program(False) - p.runTests() - - self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, - 'verbosity': verbosity, - 'failfast': failfast, - 'tb_locals': False, - 'warnings': None})]) - self.assertEqual(FakeRunner.runArgs, [test]) - self.assertEqual(p.result, result) - - self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) - - FakeRunner.initArgs = [] - FakeRunner.runArgs = [] - p = Program(True) - p.runTests() - - self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, - 'verbosity': verbosity, - 'failfast': failfast, - 'tb_locals': False, - 'warnings': None})]) - self.assertEqual(FakeRunner.runArgs, [test]) - self.assertEqual(p.result, result) - - self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) - - def testRemoveHandler(self): - default_handler = signal.getsignal(signal.SIGINT) - unittest.installHandler() - unittest.removeHandler() - self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) - - # check that calling removeHandler multiple times has no ill-effect - unittest.removeHandler() - self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) - - def testRemoveHandlerAsDecorator(self): - default_handler = signal.getsignal(signal.SIGINT) - unittest.installHandler() - - @unittest.removeHandler - def test(): - self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) - - test() - self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) - -@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") -@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") -class TestBreakDefaultIntHandler(TestBreak): - int_handler = signal.default_int_handler - -@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") -@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") -class TestBreakSignalIgnored(TestBreak): - int_handler = signal.SIG_IGN - -@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") -@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") -class TestBreakSignalDefault(TestBreak): - int_handler = signal.SIG_DFL - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_case.py b/Monika After Story/game/python-packages/unittest/test/test_case.py deleted file mode 100644 index f855c4dc00..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_case.py +++ /dev/null @@ -1,1852 +0,0 @@ -import contextlib -import difflib -import pprint -import pickle -import re -import sys -import logging -import warnings -import weakref -import inspect - -from copy import deepcopy -from test import support - -import unittest - -from unittest.test.support import ( - TestEquality, TestHashing, LoggingResult, LegacyLoggingResult, - ResultWithNoStartTestRunStopTestRun -) -from test.support import captured_stderr - - -log_foo = logging.getLogger('foo') -log_foobar = logging.getLogger('foo.bar') -log_quux = logging.getLogger('quux') - - -class Test(object): - "Keep these TestCase classes out of the main namespace" - - class Foo(unittest.TestCase): - def runTest(self): pass - def test1(self): pass - - class Bar(Foo): - def test2(self): pass - - class LoggingTestCase(unittest.TestCase): - """A test case which logs its calls.""" - - def __init__(self, events): - super(Test.LoggingTestCase, self).__init__('test') - self.events = events - - def setUp(self): - self.events.append('setUp') - - def test(self): - self.events.append('test') - - def tearDown(self): - self.events.append('tearDown') - - -class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): - - ### Set up attributes used by inherited tests - ################################################################ - - # Used by TestHashing.test_hash and TestEquality.test_eq - eq_pairs = [(Test.Foo('test1'), Test.Foo('test1'))] - - # Used by TestEquality.test_ne - ne_pairs = [(Test.Foo('test1'), Test.Foo('runTest')), - (Test.Foo('test1'), Test.Bar('test1')), - (Test.Foo('test1'), Test.Bar('test2'))] - - ################################################################ - ### /Set up attributes used by inherited tests - - - # "class TestCase([methodName])" - # ... - # "Each instance of TestCase will run a single test method: the - # method named methodName." - # ... - # "methodName defaults to "runTest"." - # - # Make sure it really is optional, and that it defaults to the proper - # thing. - def test_init__no_test_name(self): - class Test(unittest.TestCase): - def runTest(self): raise MyException() - def test(self): pass - - self.assertEqual(Test().id()[-13:], '.Test.runTest') - - # test that TestCase can be instantiated with no args - # primarily for use at the interactive interpreter - test = unittest.TestCase() - test.assertEqual(3, 3) - with test.assertRaises(test.failureException): - test.assertEqual(3, 2) - - with self.assertRaises(AttributeError): - test.run() - - # "class TestCase([methodName])" - # ... - # "Each instance of TestCase will run a single test method: the - # method named methodName." - def test_init__test_name__valid(self): - class Test(unittest.TestCase): - def runTest(self): raise MyException() - def test(self): pass - - self.assertEqual(Test('test').id()[-10:], '.Test.test') - - # "class TestCase([methodName])" - # ... - # "Each instance of TestCase will run a single test method: the - # method named methodName." - def test_init__test_name__invalid(self): - class Test(unittest.TestCase): - def runTest(self): raise MyException() - def test(self): pass - - try: - Test('testfoo') - except ValueError: - pass - else: - self.fail("Failed to raise ValueError") - - # "Return the number of tests represented by the this test object. For - # TestCase instances, this will always be 1" - def test_countTestCases(self): - class Foo(unittest.TestCase): - def test(self): pass - - self.assertEqual(Foo('test').countTestCases(), 1) - - # "Return the default type of test result object to be used to run this - # test. For TestCase instances, this will always be - # unittest.TestResult; subclasses of TestCase should - # override this as necessary." - def test_defaultTestResult(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - result = Foo().defaultTestResult() - self.assertEqual(type(result), unittest.TestResult) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if setUp() raises - # an exception. - def test_run_call_order__error_in_setUp(self): - events = [] - result = LoggingResult(events) - - class Foo(Test.LoggingTestCase): - def setUp(self): - super(Foo, self).setUp() - raise RuntimeError('raised by Foo.setUp') - - Foo(events).run(result) - expected = ['startTest', 'setUp', 'addError', 'stopTest'] - self.assertEqual(events, expected) - - # "With a temporary result stopTestRun is called when setUp errors. - def test_run_call_order__error_in_setUp_default_result(self): - events = [] - - class Foo(Test.LoggingTestCase): - def defaultTestResult(self): - return LoggingResult(self.events) - - def setUp(self): - super(Foo, self).setUp() - raise RuntimeError('raised by Foo.setUp') - - Foo(events).run() - expected = ['startTestRun', 'startTest', 'setUp', 'addError', - 'stopTest', 'stopTestRun'] - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if the test raises - # an error (as opposed to a failure). - def test_run_call_order__error_in_test(self): - events = [] - result = LoggingResult(events) - - class Foo(Test.LoggingTestCase): - def test(self): - super(Foo, self).test() - raise RuntimeError('raised by Foo.test') - - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addError', 'stopTest'] - Foo(events).run(result) - self.assertEqual(events, expected) - - # "With a default result, an error in the test still results in stopTestRun - # being called." - def test_run_call_order__error_in_test_default_result(self): - events = [] - - class Foo(Test.LoggingTestCase): - def defaultTestResult(self): - return LoggingResult(self.events) - - def test(self): - super(Foo, self).test() - raise RuntimeError('raised by Foo.test') - - expected = ['startTestRun', 'startTest', 'setUp', 'test', - 'tearDown', 'addError', 'stopTest', 'stopTestRun'] - Foo(events).run() - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if the test signals - # a failure (as opposed to an error). - def test_run_call_order__failure_in_test(self): - events = [] - result = LoggingResult(events) - - class Foo(Test.LoggingTestCase): - def test(self): - super(Foo, self).test() - self.fail('raised by Foo.test') - - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addFailure', 'stopTest'] - Foo(events).run(result) - self.assertEqual(events, expected) - - # "When a test fails with a default result stopTestRun is still called." - def test_run_call_order__failure_in_test_default_result(self): - - class Foo(Test.LoggingTestCase): - def defaultTestResult(self): - return LoggingResult(self.events) - def test(self): - super(Foo, self).test() - self.fail('raised by Foo.test') - - expected = ['startTestRun', 'startTest', 'setUp', 'test', - 'tearDown', 'addFailure', 'stopTest', 'stopTestRun'] - events = [] - Foo(events).run() - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if tearDown() raises - # an exception. - def test_run_call_order__error_in_tearDown(self): - events = [] - result = LoggingResult(events) - - class Foo(Test.LoggingTestCase): - def tearDown(self): - super(Foo, self).tearDown() - raise RuntimeError('raised by Foo.tearDown') - - Foo(events).run(result) - expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', - 'stopTest'] - self.assertEqual(events, expected) - - # "When tearDown errors with a default result stopTestRun is still called." - def test_run_call_order__error_in_tearDown_default_result(self): - - class Foo(Test.LoggingTestCase): - def defaultTestResult(self): - return LoggingResult(self.events) - def tearDown(self): - super(Foo, self).tearDown() - raise RuntimeError('raised by Foo.tearDown') - - events = [] - Foo(events).run() - expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown', - 'addError', 'stopTest', 'stopTestRun'] - self.assertEqual(events, expected) - - # "TestCase.run() still works when the defaultTestResult is a TestResult - # that does not support startTestRun and stopTestRun. - def test_run_call_order_default_result(self): - - class Foo(unittest.TestCase): - def defaultTestResult(self): - return ResultWithNoStartTestRunStopTestRun() - def test(self): - pass - - Foo('test').run() - - def _check_call_order__subtests(self, result, events, expected_events): - class Foo(Test.LoggingTestCase): - def test(self): - super(Foo, self).test() - for i in [1, 2, 3]: - with self.subTest(i=i): - if i == 1: - self.fail('failure') - for j in [2, 3]: - with self.subTest(j=j): - if i * j == 6: - raise RuntimeError('raised by Foo.test') - 1 / 0 - - # Order is the following: - # i=1 => subtest failure - # i=2, j=2 => subtest success - # i=2, j=3 => subtest error - # i=3, j=2 => subtest error - # i=3, j=3 => subtest success - # toplevel => error - Foo(events).run(result) - self.assertEqual(events, expected_events) - - def test_run_call_order__subtests(self): - events = [] - result = LoggingResult(events) - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addSubTestFailure', 'addSubTestSuccess', - 'addSubTestFailure', 'addSubTestFailure', - 'addSubTestSuccess', 'addError', 'stopTest'] - self._check_call_order__subtests(result, events, expected) - - def test_run_call_order__subtests_legacy(self): - # With a legacy result object (without an addSubTest method), - # text execution stops after the first subtest failure. - events = [] - result = LegacyLoggingResult(events) - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addFailure', 'stopTest'] - self._check_call_order__subtests(result, events, expected) - - def _check_call_order__subtests_success(self, result, events, expected_events): - class Foo(Test.LoggingTestCase): - def test(self): - super(Foo, self).test() - for i in [1, 2]: - with self.subTest(i=i): - for j in [2, 3]: - with self.subTest(j=j): - pass - - Foo(events).run(result) - self.assertEqual(events, expected_events) - - def test_run_call_order__subtests_success(self): - events = [] - result = LoggingResult(events) - # The 6 subtest successes are individually recorded, in addition - # to the whole test success. - expected = (['startTest', 'setUp', 'test', 'tearDown'] - + 6 * ['addSubTestSuccess'] - + ['addSuccess', 'stopTest']) - self._check_call_order__subtests_success(result, events, expected) - - def test_run_call_order__subtests_success_legacy(self): - # With a legacy result, only the whole test success is recorded. - events = [] - result = LegacyLoggingResult(events) - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addSuccess', 'stopTest'] - self._check_call_order__subtests_success(result, events, expected) - - def test_run_call_order__subtests_failfast(self): - events = [] - result = LoggingResult(events) - result.failfast = True - - class Foo(Test.LoggingTestCase): - def test(self): - super(Foo, self).test() - with self.subTest(i=1): - self.fail('failure') - with self.subTest(i=2): - self.fail('failure') - self.fail('failure') - - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addSubTestFailure', 'stopTest'] - Foo(events).run(result) - self.assertEqual(events, expected) - - def test_subtests_failfast(self): - # Ensure proper test flow with subtests and failfast (issue #22894) - events = [] - - class Foo(unittest.TestCase): - def test_a(self): - with self.subTest(): - events.append('a1') - events.append('a2') - - def test_b(self): - with self.subTest(): - events.append('b1') - with self.subTest(): - self.fail('failure') - events.append('b2') - - def test_c(self): - events.append('c') - - result = unittest.TestResult() - result.failfast = True - suite = unittest.makeSuite(Foo) - suite.run(result) - - expected = ['a1', 'a2', 'b1'] - self.assertEqual(events, expected) - - def test_subtests_debug(self): - # Test debug() with a test that uses subTest() (bpo-34900) - events = [] - - class Foo(unittest.TestCase): - def test_a(self): - events.append('test case') - with self.subTest(): - events.append('subtest 1') - - Foo('test_a').debug() - - self.assertEqual(events, ['test case', 'subtest 1']) - - # "This class attribute gives the exception raised by the test() method. - # If a test framework needs to use a specialized exception, possibly to - # carry additional information, it must subclass this exception in - # order to ``play fair'' with the framework. The initial value of this - # attribute is AssertionError" - def test_failureException__default(self): - class Foo(unittest.TestCase): - def test(self): - pass - - self.assertIs(Foo('test').failureException, AssertionError) - - # "This class attribute gives the exception raised by the test() method. - # If a test framework needs to use a specialized exception, possibly to - # carry additional information, it must subclass this exception in - # order to ``play fair'' with the framework." - # - # Make sure TestCase.run() respects the designated failureException - def test_failureException__subclassing__explicit_raise(self): - events = [] - result = LoggingResult(events) - - class Foo(unittest.TestCase): - def test(self): - raise RuntimeError() - - failureException = RuntimeError - - self.assertIs(Foo('test').failureException, RuntimeError) - - - Foo('test').run(result) - expected = ['startTest', 'addFailure', 'stopTest'] - self.assertEqual(events, expected) - - # "This class attribute gives the exception raised by the test() method. - # If a test framework needs to use a specialized exception, possibly to - # carry additional information, it must subclass this exception in - # order to ``play fair'' with the framework." - # - # Make sure TestCase.run() respects the designated failureException - def test_failureException__subclassing__implicit_raise(self): - events = [] - result = LoggingResult(events) - - class Foo(unittest.TestCase): - def test(self): - self.fail("foo") - - failureException = RuntimeError - - self.assertIs(Foo('test').failureException, RuntimeError) - - - Foo('test').run(result) - expected = ['startTest', 'addFailure', 'stopTest'] - self.assertEqual(events, expected) - - # "The default implementation does nothing." - def test_setUp(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - # ... and nothing should happen - Foo().setUp() - - # "The default implementation does nothing." - def test_tearDown(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - # ... and nothing should happen - Foo().tearDown() - - # "Return a string identifying the specific test case." - # - # Because of the vague nature of the docs, I'm not going to lock this - # test down too much. Really all that can be asserted is that the id() - # will be a string (either 8-byte or unicode -- again, because the docs - # just say "string") - def test_id(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - self.assertIsInstance(Foo().id(), str) - - - # "If result is omitted or None, a temporary result object is created, - # used, and is made available to the caller. As TestCase owns the - # temporary result startTestRun and stopTestRun are called. - - def test_run__uses_defaultTestResult(self): - events = [] - defaultResult = LoggingResult(events) - - class Foo(unittest.TestCase): - def test(self): - events.append('test') - - def defaultTestResult(self): - return defaultResult - - # Make run() find a result object on its own - result = Foo('test').run() - - self.assertIs(result, defaultResult) - expected = ['startTestRun', 'startTest', 'test', 'addSuccess', - 'stopTest', 'stopTestRun'] - self.assertEqual(events, expected) - - - # "The result object is returned to run's caller" - def test_run__returns_given_result(self): - - class Foo(unittest.TestCase): - def test(self): - pass - - result = unittest.TestResult() - - retval = Foo('test').run(result) - self.assertIs(retval, result) - - - # "The same effect [as method run] may be had by simply calling the - # TestCase instance." - def test_call__invoking_an_instance_delegates_to_run(self): - resultIn = unittest.TestResult() - resultOut = unittest.TestResult() - - class Foo(unittest.TestCase): - def test(self): - pass - - def run(self, result): - self.assertIs(result, resultIn) - return resultOut - - retval = Foo('test')(resultIn) - - self.assertIs(retval, resultOut) - - - def testShortDescriptionWithoutDocstring(self): - self.assertIsNone(self.shortDescription()) - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testShortDescriptionWithOneLineDocstring(self): - """Tests shortDescription() for a method with a docstring.""" - self.assertEqual( - self.shortDescription(), - 'Tests shortDescription() for a method with a docstring.') - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testShortDescriptionWithMultiLineDocstring(self): - """Tests shortDescription() for a method with a longer docstring. - - This method ensures that only the first line of a docstring is - returned used in the short description, no matter how long the - whole thing is. - """ - self.assertEqual( - self.shortDescription(), - 'Tests shortDescription() for a method with a longer ' - 'docstring.') - - def testShortDescriptionWhitespaceTrimming(self): - """ - Tests shortDescription() whitespace is trimmed, so that the first - line of nonwhite-space text becomes the docstring. - """ - self.assertEqual( - self.shortDescription(), - 'Tests shortDescription() whitespace is trimmed, so that the first') - - def testAddTypeEqualityFunc(self): - class SadSnake(object): - """Dummy class for test_addTypeEqualityFunc.""" - s1, s2 = SadSnake(), SadSnake() - self.assertFalse(s1 == s2) - def AllSnakesCreatedEqual(a, b, msg=None): - return type(a) == type(b) == SadSnake - self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) - self.assertEqual(s1, s2) - # No this doesn't clean up and remove the SadSnake equality func - # from this TestCase instance but since it's local nothing else - # will ever notice that. - - def testAssertIs(self): - thing = object() - self.assertIs(thing, thing) - self.assertRaises(self.failureException, self.assertIs, thing, object()) - - def testAssertIsNot(self): - thing = object() - self.assertIsNot(thing, object()) - self.assertRaises(self.failureException, self.assertIsNot, thing, thing) - - def testAssertIsInstance(self): - thing = [] - self.assertIsInstance(thing, list) - self.assertRaises(self.failureException, self.assertIsInstance, - thing, dict) - - def testAssertNotIsInstance(self): - thing = [] - self.assertNotIsInstance(thing, dict) - self.assertRaises(self.failureException, self.assertNotIsInstance, - thing, list) - - def testAssertIn(self): - animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} - - self.assertIn('a', 'abc') - self.assertIn(2, [1, 2, 3]) - self.assertIn('monkey', animals) - - self.assertNotIn('d', 'abc') - self.assertNotIn(0, [1, 2, 3]) - self.assertNotIn('otter', animals) - - self.assertRaises(self.failureException, self.assertIn, 'x', 'abc') - self.assertRaises(self.failureException, self.assertIn, 4, [1, 2, 3]) - self.assertRaises(self.failureException, self.assertIn, 'elephant', - animals) - - self.assertRaises(self.failureException, self.assertNotIn, 'c', 'abc') - self.assertRaises(self.failureException, self.assertNotIn, 1, [1, 2, 3]) - self.assertRaises(self.failureException, self.assertNotIn, 'cow', - animals) - - def testAssertDictContainsSubset(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - - self.assertDictContainsSubset({}, {}) - self.assertDictContainsSubset({}, {'a': 1}) - self.assertDictContainsSubset({'a': 1}, {'a': 1}) - self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2}) - self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2}) - - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({1: "one"}, {}) - - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({'a': 2}, {'a': 1}) - - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({'c': 1}, {'a': 1}) - - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) - - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1}) - - one = ''.join(chr(i) for i in range(255)) - # this used to cause a UnicodeDecodeError constructing the failure msg - with self.assertRaises(self.failureException): - self.assertDictContainsSubset({'foo': one}, {'foo': '\uFFFD'}) - - def testAssertEqual(self): - equal_pairs = [ - ((), ()), - ({}, {}), - ([], []), - (set(), set()), - (frozenset(), frozenset())] - for a, b in equal_pairs: - # This mess of try excepts is to test the assertEqual behavior - # itself. - try: - self.assertEqual(a, b) - except self.failureException: - self.fail('assertEqual(%r, %r) failed' % (a, b)) - try: - self.assertEqual(a, b, msg='foo') - except self.failureException: - self.fail('assertEqual(%r, %r) with msg= failed' % (a, b)) - try: - self.assertEqual(a, b, 'foo') - except self.failureException: - self.fail('assertEqual(%r, %r) with third parameter failed' % - (a, b)) - - unequal_pairs = [ - ((), []), - ({}, set()), - (set([4,1]), frozenset([4,2])), - (frozenset([4,5]), set([2,3])), - (set([3,4]), set([5,4]))] - for a, b in unequal_pairs: - self.assertRaises(self.failureException, self.assertEqual, a, b) - self.assertRaises(self.failureException, self.assertEqual, a, b, - 'foo') - self.assertRaises(self.failureException, self.assertEqual, a, b, - msg='foo') - - def testEquality(self): - self.assertListEqual([], []) - self.assertTupleEqual((), ()) - self.assertSequenceEqual([], ()) - - a = [0, 'a', []] - b = [] - self.assertRaises(unittest.TestCase.failureException, - self.assertListEqual, a, b) - self.assertRaises(unittest.TestCase.failureException, - self.assertListEqual, tuple(a), tuple(b)) - self.assertRaises(unittest.TestCase.failureException, - self.assertSequenceEqual, a, tuple(b)) - - b.extend(a) - self.assertListEqual(a, b) - self.assertTupleEqual(tuple(a), tuple(b)) - self.assertSequenceEqual(a, tuple(b)) - self.assertSequenceEqual(tuple(a), b) - - self.assertRaises(self.failureException, self.assertListEqual, - a, tuple(b)) - self.assertRaises(self.failureException, self.assertTupleEqual, - tuple(a), b) - self.assertRaises(self.failureException, self.assertListEqual, None, b) - self.assertRaises(self.failureException, self.assertTupleEqual, None, - tuple(b)) - self.assertRaises(self.failureException, self.assertSequenceEqual, - None, tuple(b)) - self.assertRaises(self.failureException, self.assertListEqual, 1, 1) - self.assertRaises(self.failureException, self.assertTupleEqual, 1, 1) - self.assertRaises(self.failureException, self.assertSequenceEqual, - 1, 1) - - self.assertDictEqual({}, {}) - - c = { 'x': 1 } - d = {} - self.assertRaises(unittest.TestCase.failureException, - self.assertDictEqual, c, d) - - d.update(c) - self.assertDictEqual(c, d) - - d['x'] = 0 - self.assertRaises(unittest.TestCase.failureException, - self.assertDictEqual, c, d, 'These are unequal') - - self.assertRaises(self.failureException, self.assertDictEqual, None, d) - self.assertRaises(self.failureException, self.assertDictEqual, [], d) - self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) - - def testAssertSequenceEqualMaxDiff(self): - self.assertEqual(self.maxDiff, 80*8) - seq1 = 'a' + 'x' * 80**2 - seq2 = 'b' + 'x' * 80**2 - diff = '\n'.join(difflib.ndiff(pprint.pformat(seq1).splitlines(), - pprint.pformat(seq2).splitlines())) - # the +1 is the leading \n added by assertSequenceEqual - omitted = unittest.case.DIFF_OMITTED % (len(diff) + 1,) - - self.maxDiff = len(diff)//2 - try: - - self.assertSequenceEqual(seq1, seq2) - except self.failureException as e: - msg = e.args[0] - else: - self.fail('assertSequenceEqual did not fail.') - self.assertLess(len(msg), len(diff)) - self.assertIn(omitted, msg) - - self.maxDiff = len(diff) * 2 - try: - self.assertSequenceEqual(seq1, seq2) - except self.failureException as e: - msg = e.args[0] - else: - self.fail('assertSequenceEqual did not fail.') - self.assertGreater(len(msg), len(diff)) - self.assertNotIn(omitted, msg) - - self.maxDiff = None - try: - self.assertSequenceEqual(seq1, seq2) - except self.failureException as e: - msg = e.args[0] - else: - self.fail('assertSequenceEqual did not fail.') - self.assertGreater(len(msg), len(diff)) - self.assertNotIn(omitted, msg) - - def testTruncateMessage(self): - self.maxDiff = 1 - message = self._truncateMessage('foo', 'bar') - omitted = unittest.case.DIFF_OMITTED % len('bar') - self.assertEqual(message, 'foo' + omitted) - - self.maxDiff = None - message = self._truncateMessage('foo', 'bar') - self.assertEqual(message, 'foobar') - - self.maxDiff = 4 - message = self._truncateMessage('foo', 'bar') - self.assertEqual(message, 'foobar') - - def testAssertDictEqualTruncates(self): - test = unittest.TestCase('assertEqual') - def truncate(msg, diff): - return 'foo' - test._truncateMessage = truncate - try: - test.assertDictEqual({}, {1: 0}) - except self.failureException as e: - self.assertEqual(str(e), 'foo') - else: - self.fail('assertDictEqual did not fail') - - def testAssertMultiLineEqualTruncates(self): - test = unittest.TestCase('assertEqual') - def truncate(msg, diff): - return 'foo' - test._truncateMessage = truncate - try: - test.assertMultiLineEqual('foo', 'bar') - except self.failureException as e: - self.assertEqual(str(e), 'foo') - else: - self.fail('assertMultiLineEqual did not fail') - - def testAssertEqual_diffThreshold(self): - # check threshold value - self.assertEqual(self._diffThreshold, 2**16) - # disable madDiff to get diff markers - self.maxDiff = None - - # set a lower threshold value and add a cleanup to restore it - old_threshold = self._diffThreshold - self._diffThreshold = 2**5 - self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) - - # under the threshold: diff marker (^) in error message - s = 'x' * (2**4) - with self.assertRaises(self.failureException) as cm: - self.assertEqual(s + 'a', s + 'b') - self.assertIn('^', str(cm.exception)) - self.assertEqual(s + 'a', s + 'a') - - # over the threshold: diff not used and marker (^) not in error message - s = 'x' * (2**6) - # if the path that uses difflib is taken, _truncateMessage will be - # called -- replace it with explodingTruncation to verify that this - # doesn't happen - def explodingTruncation(message, diff): - raise SystemError('this should not be raised') - old_truncate = self._truncateMessage - self._truncateMessage = explodingTruncation - self.addCleanup(lambda: setattr(self, '_truncateMessage', old_truncate)) - - s1, s2 = s + 'a', s + 'b' - with self.assertRaises(self.failureException) as cm: - self.assertEqual(s1, s2) - self.assertNotIn('^', str(cm.exception)) - self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2)) - self.assertEqual(s + 'a', s + 'a') - - def testAssertEqual_shorten(self): - # set a lower threshold value and add a cleanup to restore it - old_threshold = self._diffThreshold - self._diffThreshold = 0 - self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) - - s = 'x' * 100 - s1, s2 = s + 'a', s + 'b' - with self.assertRaises(self.failureException) as cm: - self.assertEqual(s1, s2) - c = 'xxxx[35 chars]' + 'x' * 61 - self.assertEqual(str(cm.exception), "'%sa' != '%sb'" % (c, c)) - self.assertEqual(s + 'a', s + 'a') - - p = 'y' * 50 - s1, s2 = s + 'a' + p, s + 'b' + p - with self.assertRaises(self.failureException) as cm: - self.assertEqual(s1, s2) - c = 'xxxx[85 chars]xxxxxxxxxxx' - self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, p, c, p)) - - p = 'y' * 100 - s1, s2 = s + 'a' + p, s + 'b' + p - with self.assertRaises(self.failureException) as cm: - self.assertEqual(s1, s2) - c = 'xxxx[91 chars]xxxxx' - d = 'y' * 40 + '[56 chars]yyyy' - self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, d, c, d)) - - def testAssertCountEqual(self): - a = object() - self.assertCountEqual([1, 2, 3], [3, 2, 1]) - self.assertCountEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) - self.assertCountEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2)) - self.assertCountEqual([1, "2", "a", "a"], ["a", "2", True, "a"]) - self.assertRaises(self.failureException, self.assertCountEqual, - [1, 2] + [3] * 100, [1] * 100 + [2, 3]) - self.assertRaises(self.failureException, self.assertCountEqual, - [1, "2", "a", "a"], ["a", "2", True, 1]) - self.assertRaises(self.failureException, self.assertCountEqual, - [10], [10, 11]) - self.assertRaises(self.failureException, self.assertCountEqual, - [10, 11], [10]) - self.assertRaises(self.failureException, self.assertCountEqual, - [10, 11, 10], [10, 11]) - - # Test that sequences of unhashable objects can be tested for sameness: - self.assertCountEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]]) - # Test that iterator of unhashable objects can be tested for sameness: - self.assertCountEqual(iter([1, 2, [], 3, 4]), - iter([1, 2, [], 3, 4])) - - # hashable types, but not orderable - self.assertRaises(self.failureException, self.assertCountEqual, - [], [divmod, 'x', 1, 5j, 2j, frozenset()]) - # comparing dicts - self.assertCountEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) - # comparing heterogeneous non-hashable sequences - self.assertCountEqual([1, 'x', divmod, []], [divmod, [], 'x', 1]) - self.assertRaises(self.failureException, self.assertCountEqual, - [], [divmod, [], 'x', 1, 5j, 2j, set()]) - self.assertRaises(self.failureException, self.assertCountEqual, - [[1]], [[2]]) - - # Same elements, but not same sequence length - self.assertRaises(self.failureException, self.assertCountEqual, - [1, 1, 2], [2, 1]) - self.assertRaises(self.failureException, self.assertCountEqual, - [1, 1, "2", "a", "a"], ["2", "2", True, "a"]) - self.assertRaises(self.failureException, self.assertCountEqual, - [1, {'b': 2}, None, True], [{'b': 2}, True, None]) - - # Same elements which don't reliably compare, in - # different order, see issue 10242 - a = [{2,4}, {1,2}] - b = a[::-1] - self.assertCountEqual(a, b) - - # test utility functions supporting assertCountEqual() - - diffs = set(unittest.util._count_diff_all_purpose('aaabccd', 'abbbcce')) - expected = {(3,1,'a'), (1,3,'b'), (1,0,'d'), (0,1,'e')} - self.assertEqual(diffs, expected) - - diffs = unittest.util._count_diff_all_purpose([[]], []) - self.assertEqual(diffs, [(1, 0, [])]) - - diffs = set(unittest.util._count_diff_hashable('aaabccd', 'abbbcce')) - expected = {(3,1,'a'), (1,3,'b'), (1,0,'d'), (0,1,'e')} - self.assertEqual(diffs, expected) - - def testAssertSetEqual(self): - set1 = set() - set2 = set() - self.assertSetEqual(set1, set2) - - self.assertRaises(self.failureException, self.assertSetEqual, None, set2) - self.assertRaises(self.failureException, self.assertSetEqual, [], set2) - self.assertRaises(self.failureException, self.assertSetEqual, set1, None) - self.assertRaises(self.failureException, self.assertSetEqual, set1, []) - - set1 = set(['a']) - set2 = set() - self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) - - set1 = set(['a']) - set2 = set(['a']) - self.assertSetEqual(set1, set2) - - set1 = set(['a']) - set2 = set(['a', 'b']) - self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) - - set1 = set(['a']) - set2 = frozenset(['a', 'b']) - self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) - - set1 = set(['a', 'b']) - set2 = frozenset(['a', 'b']) - self.assertSetEqual(set1, set2) - - set1 = set() - set2 = "foo" - self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) - self.assertRaises(self.failureException, self.assertSetEqual, set2, set1) - - # make sure any string formatting is tuple-safe - set1 = set([(0, 1), (2, 3)]) - set2 = set([(4, 5)]) - self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) - - def testInequality(self): - # Try ints - self.assertGreater(2, 1) - self.assertGreaterEqual(2, 1) - self.assertGreaterEqual(1, 1) - self.assertLess(1, 2) - self.assertLessEqual(1, 2) - self.assertLessEqual(1, 1) - self.assertRaises(self.failureException, self.assertGreater, 1, 2) - self.assertRaises(self.failureException, self.assertGreater, 1, 1) - self.assertRaises(self.failureException, self.assertGreaterEqual, 1, 2) - self.assertRaises(self.failureException, self.assertLess, 2, 1) - self.assertRaises(self.failureException, self.assertLess, 1, 1) - self.assertRaises(self.failureException, self.assertLessEqual, 2, 1) - - # Try Floats - self.assertGreater(1.1, 1.0) - self.assertGreaterEqual(1.1, 1.0) - self.assertGreaterEqual(1.0, 1.0) - self.assertLess(1.0, 1.1) - self.assertLessEqual(1.0, 1.1) - self.assertLessEqual(1.0, 1.0) - self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.1) - self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.0) - self.assertRaises(self.failureException, self.assertGreaterEqual, 1.0, 1.1) - self.assertRaises(self.failureException, self.assertLess, 1.1, 1.0) - self.assertRaises(self.failureException, self.assertLess, 1.0, 1.0) - self.assertRaises(self.failureException, self.assertLessEqual, 1.1, 1.0) - - # Try Strings - self.assertGreater('bug', 'ant') - self.assertGreaterEqual('bug', 'ant') - self.assertGreaterEqual('ant', 'ant') - self.assertLess('ant', 'bug') - self.assertLessEqual('ant', 'bug') - self.assertLessEqual('ant', 'ant') - self.assertRaises(self.failureException, self.assertGreater, 'ant', 'bug') - self.assertRaises(self.failureException, self.assertGreater, 'ant', 'ant') - self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', 'bug') - self.assertRaises(self.failureException, self.assertLess, 'bug', 'ant') - self.assertRaises(self.failureException, self.assertLess, 'ant', 'ant') - self.assertRaises(self.failureException, self.assertLessEqual, 'bug', 'ant') - - # Try bytes - self.assertGreater(b'bug', b'ant') - self.assertGreaterEqual(b'bug', b'ant') - self.assertGreaterEqual(b'ant', b'ant') - self.assertLess(b'ant', b'bug') - self.assertLessEqual(b'ant', b'bug') - self.assertLessEqual(b'ant', b'ant') - self.assertRaises(self.failureException, self.assertGreater, b'ant', b'bug') - self.assertRaises(self.failureException, self.assertGreater, b'ant', b'ant') - self.assertRaises(self.failureException, self.assertGreaterEqual, b'ant', - b'bug') - self.assertRaises(self.failureException, self.assertLess, b'bug', b'ant') - self.assertRaises(self.failureException, self.assertLess, b'ant', b'ant') - self.assertRaises(self.failureException, self.assertLessEqual, b'bug', b'ant') - - def testAssertMultiLineEqual(self): - sample_text = """\ -http://www.python.org/doc/2.3/lib/module-unittest.html -test case - A test case is the smallest unit of testing. [...] -""" - revised_sample_text = """\ -http://www.python.org/doc/2.4.1/lib/module-unittest.html -test case - A test case is the smallest unit of testing. [...] You may provide your - own implementation that does not subclass from TestCase, of course. -""" - sample_text_error = """\ -- http://www.python.org/doc/2.3/lib/module-unittest.html -? ^ -+ http://www.python.org/doc/2.4.1/lib/module-unittest.html -? ^^^ - test case -- A test case is the smallest unit of testing. [...] -+ A test case is the smallest unit of testing. [...] You may provide your -? +++++++++++++++++++++ -+ own implementation that does not subclass from TestCase, of course. -""" - self.maxDiff = None - try: - self.assertMultiLineEqual(sample_text, revised_sample_text) - except self.failureException as e: - # need to remove the first line of the error message - error = str(e).split('\n', 1)[1] - self.assertEqual(sample_text_error, error) - - def testAssertEqualSingleLine(self): - sample_text = "laden swallows fly slowly" - revised_sample_text = "unladen swallows fly quickly" - sample_text_error = """\ -- laden swallows fly slowly -? ^^^^ -+ unladen swallows fly quickly -? ++ ^^^^^ -""" - try: - self.assertEqual(sample_text, revised_sample_text) - except self.failureException as e: - # need to remove the first line of the error message - error = str(e).split('\n', 1)[1] - self.assertEqual(sample_text_error, error) - - def testEqualityBytesWarning(self): - if sys.flags.bytes_warning: - def bytes_warning(): - return self.assertWarnsRegex(BytesWarning, - 'Comparison between bytes and string') - else: - def bytes_warning(): - return contextlib.ExitStack() - - with bytes_warning(), self.assertRaises(self.failureException): - self.assertEqual('a', b'a') - with bytes_warning(): - self.assertNotEqual('a', b'a') - - a = [0, 'a'] - b = [0, b'a'] - with bytes_warning(), self.assertRaises(self.failureException): - self.assertListEqual(a, b) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertTupleEqual(tuple(a), tuple(b)) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertSequenceEqual(a, tuple(b)) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertSequenceEqual(tuple(a), b) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertSequenceEqual('a', b'a') - with bytes_warning(), self.assertRaises(self.failureException): - self.assertSetEqual(set(a), set(b)) - - with self.assertRaises(self.failureException): - self.assertListEqual(a, tuple(b)) - with self.assertRaises(self.failureException): - self.assertTupleEqual(tuple(a), b) - - a = [0, b'a'] - b = [0] - with self.assertRaises(self.failureException): - self.assertListEqual(a, b) - with self.assertRaises(self.failureException): - self.assertTupleEqual(tuple(a), tuple(b)) - with self.assertRaises(self.failureException): - self.assertSequenceEqual(a, tuple(b)) - with self.assertRaises(self.failureException): - self.assertSequenceEqual(tuple(a), b) - with self.assertRaises(self.failureException): - self.assertSetEqual(set(a), set(b)) - - a = [0] - b = [0, b'a'] - with self.assertRaises(self.failureException): - self.assertListEqual(a, b) - with self.assertRaises(self.failureException): - self.assertTupleEqual(tuple(a), tuple(b)) - with self.assertRaises(self.failureException): - self.assertSequenceEqual(a, tuple(b)) - with self.assertRaises(self.failureException): - self.assertSequenceEqual(tuple(a), b) - with self.assertRaises(self.failureException): - self.assertSetEqual(set(a), set(b)) - - with bytes_warning(), self.assertRaises(self.failureException): - self.assertDictEqual({'a': 0}, {b'a': 0}) - with self.assertRaises(self.failureException): - self.assertDictEqual({}, {b'a': 0}) - with self.assertRaises(self.failureException): - self.assertDictEqual({b'a': 0}, {}) - - with self.assertRaises(self.failureException): - self.assertCountEqual([b'a', b'a'], [b'a', b'a', b'a']) - with bytes_warning(): - self.assertCountEqual(['a', b'a'], ['a', b'a']) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertCountEqual(['a', 'a'], [b'a', b'a']) - with bytes_warning(), self.assertRaises(self.failureException): - self.assertCountEqual(['a', 'a', []], [b'a', b'a', []]) - - def testAssertIsNone(self): - self.assertIsNone(None) - self.assertRaises(self.failureException, self.assertIsNone, False) - self.assertIsNotNone('DjZoPloGears on Rails') - self.assertRaises(self.failureException, self.assertIsNotNone, None) - - def testAssertRegex(self): - self.assertRegex('asdfabasdf', r'ab+') - self.assertRaises(self.failureException, self.assertRegex, - 'saaas', r'aaaa') - - def testAssertRaisesCallable(self): - class ExceptionMock(Exception): - pass - def Stub(): - raise ExceptionMock('We expect') - self.assertRaises(ExceptionMock, Stub) - # A tuple of exception classes is accepted - self.assertRaises((ValueError, ExceptionMock), Stub) - # *args and **kwargs also work - self.assertRaises(ValueError, int, '19', base=8) - # Failure when no exception is raised - with self.assertRaises(self.failureException): - self.assertRaises(ExceptionMock, lambda: 0) - # Failure when the function is None - with self.assertRaises(TypeError): - self.assertRaises(ExceptionMock, None) - # Failure when another exception is raised - with self.assertRaises(ExceptionMock): - self.assertRaises(ValueError, Stub) - - def testAssertRaisesContext(self): - class ExceptionMock(Exception): - pass - def Stub(): - raise ExceptionMock('We expect') - with self.assertRaises(ExceptionMock): - Stub() - # A tuple of exception classes is accepted - with self.assertRaises((ValueError, ExceptionMock)) as cm: - Stub() - # The context manager exposes caught exception - self.assertIsInstance(cm.exception, ExceptionMock) - self.assertEqual(cm.exception.args[0], 'We expect') - # *args and **kwargs also work - with self.assertRaises(ValueError): - int('19', base=8) - # Failure when no exception is raised - with self.assertRaises(self.failureException): - with self.assertRaises(ExceptionMock): - pass - # Custom message - with self.assertRaisesRegex(self.failureException, 'foobar'): - with self.assertRaises(ExceptionMock, msg='foobar'): - pass - # Invalid keyword argument - with self.assertRaisesRegex(TypeError, 'foobar'): - with self.assertRaises(ExceptionMock, foobar=42): - pass - # Failure when another exception is raised - with self.assertRaises(ExceptionMock): - self.assertRaises(ValueError, Stub) - - def testAssertRaisesNoExceptionType(self): - with self.assertRaises(TypeError): - self.assertRaises() - with self.assertRaises(TypeError): - self.assertRaises(1) - with self.assertRaises(TypeError): - self.assertRaises(object) - with self.assertRaises(TypeError): - self.assertRaises((ValueError, 1)) - with self.assertRaises(TypeError): - self.assertRaises((ValueError, object)) - - def testAssertRaisesRefcount(self): - # bpo-23890: assertRaises() must not keep objects alive longer - # than expected - def func() : - try: - raise ValueError - except ValueError: - raise ValueError - - refcount = sys.getrefcount(func) - self.assertRaises(ValueError, func) - self.assertEqual(refcount, sys.getrefcount(func)) - - def testAssertRaisesRegex(self): - class ExceptionMock(Exception): - pass - - def Stub(): - raise ExceptionMock('We expect') - - self.assertRaisesRegex(ExceptionMock, re.compile('expect$'), Stub) - self.assertRaisesRegex(ExceptionMock, 'expect$', Stub) - with self.assertRaises(TypeError): - self.assertRaisesRegex(ExceptionMock, 'expect$', None) - - def testAssertNotRaisesRegex(self): - self.assertRaisesRegex( - self.failureException, '^Exception not raised by $', - self.assertRaisesRegex, Exception, re.compile('x'), - lambda: None) - self.assertRaisesRegex( - self.failureException, '^Exception not raised by $', - self.assertRaisesRegex, Exception, 'x', - lambda: None) - # Custom message - with self.assertRaisesRegex(self.failureException, 'foobar'): - with self.assertRaisesRegex(Exception, 'expect', msg='foobar'): - pass - # Invalid keyword argument - with self.assertRaisesRegex(TypeError, 'foobar'): - with self.assertRaisesRegex(Exception, 'expect', foobar=42): - pass - - def testAssertRaisesRegexInvalidRegex(self): - # Issue 20145. - class MyExc(Exception): - pass - self.assertRaises(TypeError, self.assertRaisesRegex, MyExc, lambda: True) - - def testAssertWarnsRegexInvalidRegex(self): - # Issue 20145. - class MyWarn(Warning): - pass - self.assertRaises(TypeError, self.assertWarnsRegex, MyWarn, lambda: True) - - def testAssertRaisesRegexMismatch(self): - def Stub(): - raise Exception('Unexpected') - - self.assertRaisesRegex( - self.failureException, - r'"\^Expected\$" does not match "Unexpected"', - self.assertRaisesRegex, Exception, '^Expected$', - Stub) - self.assertRaisesRegex( - self.failureException, - r'"\^Expected\$" does not match "Unexpected"', - self.assertRaisesRegex, Exception, - re.compile('^Expected$'), Stub) - - def testAssertRaisesExcValue(self): - class ExceptionMock(Exception): - pass - - def Stub(foo): - raise ExceptionMock(foo) - v = "particular value" - - ctx = self.assertRaises(ExceptionMock) - with ctx: - Stub(v) - e = ctx.exception - self.assertIsInstance(e, ExceptionMock) - self.assertEqual(e.args[0], v) - - def testAssertRaisesRegexNoExceptionType(self): - with self.assertRaises(TypeError): - self.assertRaisesRegex() - with self.assertRaises(TypeError): - self.assertRaisesRegex(ValueError) - with self.assertRaises(TypeError): - self.assertRaisesRegex(1, 'expect') - with self.assertRaises(TypeError): - self.assertRaisesRegex(object, 'expect') - with self.assertRaises(TypeError): - self.assertRaisesRegex((ValueError, 1), 'expect') - with self.assertRaises(TypeError): - self.assertRaisesRegex((ValueError, object), 'expect') - - def testAssertWarnsCallable(self): - def _runtime_warn(): - warnings.warn("foo", RuntimeWarning) - # Success when the right warning is triggered, even several times - self.assertWarns(RuntimeWarning, _runtime_warn) - self.assertWarns(RuntimeWarning, _runtime_warn) - # A tuple of warning classes is accepted - self.assertWarns((DeprecationWarning, RuntimeWarning), _runtime_warn) - # *args and **kwargs also work - self.assertWarns(RuntimeWarning, - warnings.warn, "foo", category=RuntimeWarning) - # Failure when no warning is triggered - with self.assertRaises(self.failureException): - self.assertWarns(RuntimeWarning, lambda: 0) - # Failure when the function is None - with self.assertRaises(TypeError): - self.assertWarns(RuntimeWarning, None) - # Failure when another warning is triggered - with warnings.catch_warnings(): - # Force default filter (in case tests are run with -We) - warnings.simplefilter("default", RuntimeWarning) - with self.assertRaises(self.failureException): - self.assertWarns(DeprecationWarning, _runtime_warn) - # Filters for other warnings are not modified - with warnings.catch_warnings(): - warnings.simplefilter("error", RuntimeWarning) - with self.assertRaises(RuntimeWarning): - self.assertWarns(DeprecationWarning, _runtime_warn) - - def testAssertWarnsContext(self): - # Believe it or not, it is preferable to duplicate all tests above, - # to make sure the __warningregistry__ $@ is circumvented correctly. - def _runtime_warn(): - warnings.warn("foo", RuntimeWarning) - _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1] - with self.assertWarns(RuntimeWarning) as cm: - _runtime_warn() - # A tuple of warning classes is accepted - with self.assertWarns((DeprecationWarning, RuntimeWarning)) as cm: - _runtime_warn() - # The context manager exposes various useful attributes - self.assertIsInstance(cm.warning, RuntimeWarning) - self.assertEqual(cm.warning.args[0], "foo") - self.assertIn("test_case.py", cm.filename) - self.assertEqual(cm.lineno, _runtime_warn_lineno + 1) - # Same with several warnings - with self.assertWarns(RuntimeWarning): - _runtime_warn() - _runtime_warn() - with self.assertWarns(RuntimeWarning): - warnings.warn("foo", category=RuntimeWarning) - # Failure when no warning is triggered - with self.assertRaises(self.failureException): - with self.assertWarns(RuntimeWarning): - pass - # Custom message - with self.assertRaisesRegex(self.failureException, 'foobar'): - with self.assertWarns(RuntimeWarning, msg='foobar'): - pass - # Invalid keyword argument - with self.assertRaisesRegex(TypeError, 'foobar'): - with self.assertWarns(RuntimeWarning, foobar=42): - pass - # Failure when another warning is triggered - with warnings.catch_warnings(): - # Force default filter (in case tests are run with -We) - warnings.simplefilter("default", RuntimeWarning) - with self.assertRaises(self.failureException): - with self.assertWarns(DeprecationWarning): - _runtime_warn() - # Filters for other warnings are not modified - with warnings.catch_warnings(): - warnings.simplefilter("error", RuntimeWarning) - with self.assertRaises(RuntimeWarning): - with self.assertWarns(DeprecationWarning): - _runtime_warn() - - def testAssertWarnsNoExceptionType(self): - with self.assertRaises(TypeError): - self.assertWarns() - with self.assertRaises(TypeError): - self.assertWarns(1) - with self.assertRaises(TypeError): - self.assertWarns(object) - with self.assertRaises(TypeError): - self.assertWarns((UserWarning, 1)) - with self.assertRaises(TypeError): - self.assertWarns((UserWarning, object)) - with self.assertRaises(TypeError): - self.assertWarns((UserWarning, Exception)) - - def testAssertWarnsRegexCallable(self): - def _runtime_warn(msg): - warnings.warn(msg, RuntimeWarning) - self.assertWarnsRegex(RuntimeWarning, "o+", - _runtime_warn, "foox") - # Failure when no warning is triggered - with self.assertRaises(self.failureException): - self.assertWarnsRegex(RuntimeWarning, "o+", - lambda: 0) - # Failure when the function is None - with self.assertRaises(TypeError): - self.assertWarnsRegex(RuntimeWarning, "o+", None) - # Failure when another warning is triggered - with warnings.catch_warnings(): - # Force default filter (in case tests are run with -We) - warnings.simplefilter("default", RuntimeWarning) - with self.assertRaises(self.failureException): - self.assertWarnsRegex(DeprecationWarning, "o+", - _runtime_warn, "foox") - # Failure when message doesn't match - with self.assertRaises(self.failureException): - self.assertWarnsRegex(RuntimeWarning, "o+", - _runtime_warn, "barz") - # A little trickier: we ask RuntimeWarnings to be raised, and then - # check for some of them. It is implementation-defined whether - # non-matching RuntimeWarnings are simply re-raised, or produce a - # failureException. - with warnings.catch_warnings(): - warnings.simplefilter("error", RuntimeWarning) - with self.assertRaises((RuntimeWarning, self.failureException)): - self.assertWarnsRegex(RuntimeWarning, "o+", - _runtime_warn, "barz") - - def testAssertWarnsRegexContext(self): - # Same as above, but with assertWarnsRegex as a context manager - def _runtime_warn(msg): - warnings.warn(msg, RuntimeWarning) - _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1] - with self.assertWarnsRegex(RuntimeWarning, "o+") as cm: - _runtime_warn("foox") - self.assertIsInstance(cm.warning, RuntimeWarning) - self.assertEqual(cm.warning.args[0], "foox") - self.assertIn("test_case.py", cm.filename) - self.assertEqual(cm.lineno, _runtime_warn_lineno + 1) - # Failure when no warning is triggered - with self.assertRaises(self.failureException): - with self.assertWarnsRegex(RuntimeWarning, "o+"): - pass - # Custom message - with self.assertRaisesRegex(self.failureException, 'foobar'): - with self.assertWarnsRegex(RuntimeWarning, 'o+', msg='foobar'): - pass - # Invalid keyword argument - with self.assertRaisesRegex(TypeError, 'foobar'): - with self.assertWarnsRegex(RuntimeWarning, 'o+', foobar=42): - pass - # Failure when another warning is triggered - with warnings.catch_warnings(): - # Force default filter (in case tests are run with -We) - warnings.simplefilter("default", RuntimeWarning) - with self.assertRaises(self.failureException): - with self.assertWarnsRegex(DeprecationWarning, "o+"): - _runtime_warn("foox") - # Failure when message doesn't match - with self.assertRaises(self.failureException): - with self.assertWarnsRegex(RuntimeWarning, "o+"): - _runtime_warn("barz") - # A little trickier: we ask RuntimeWarnings to be raised, and then - # check for some of them. It is implementation-defined whether - # non-matching RuntimeWarnings are simply re-raised, or produce a - # failureException. - with warnings.catch_warnings(): - warnings.simplefilter("error", RuntimeWarning) - with self.assertRaises((RuntimeWarning, self.failureException)): - with self.assertWarnsRegex(RuntimeWarning, "o+"): - _runtime_warn("barz") - - def testAssertWarnsRegexNoExceptionType(self): - with self.assertRaises(TypeError): - self.assertWarnsRegex() - with self.assertRaises(TypeError): - self.assertWarnsRegex(UserWarning) - with self.assertRaises(TypeError): - self.assertWarnsRegex(1, 'expect') - with self.assertRaises(TypeError): - self.assertWarnsRegex(object, 'expect') - with self.assertRaises(TypeError): - self.assertWarnsRegex((UserWarning, 1), 'expect') - with self.assertRaises(TypeError): - self.assertWarnsRegex((UserWarning, object), 'expect') - with self.assertRaises(TypeError): - self.assertWarnsRegex((UserWarning, Exception), 'expect') - - @contextlib.contextmanager - def assertNoStderr(self): - with captured_stderr() as buf: - yield - self.assertEqual(buf.getvalue(), "") - - def assertLogRecords(self, records, matches): - self.assertEqual(len(records), len(matches)) - for rec, match in zip(records, matches): - self.assertIsInstance(rec, logging.LogRecord) - for k, v in match.items(): - self.assertEqual(getattr(rec, k), v) - - def testAssertLogsDefaults(self): - # defaults: root logger, level INFO - with self.assertNoStderr(): - with self.assertLogs() as cm: - log_foo.info("1") - log_foobar.debug("2") - self.assertEqual(cm.output, ["INFO:foo:1"]) - self.assertLogRecords(cm.records, [{'name': 'foo'}]) - - def testAssertLogsTwoMatchingMessages(self): - # Same, but with two matching log messages - with self.assertNoStderr(): - with self.assertLogs() as cm: - log_foo.info("1") - log_foobar.debug("2") - log_quux.warning("3") - self.assertEqual(cm.output, ["INFO:foo:1", "WARNING:quux:3"]) - self.assertLogRecords(cm.records, - [{'name': 'foo'}, {'name': 'quux'}]) - - def checkAssertLogsPerLevel(self, level): - # Check level filtering - with self.assertNoStderr(): - with self.assertLogs(level=level) as cm: - log_foo.warning("1") - log_foobar.error("2") - log_quux.critical("3") - self.assertEqual(cm.output, ["ERROR:foo.bar:2", "CRITICAL:quux:3"]) - self.assertLogRecords(cm.records, - [{'name': 'foo.bar'}, {'name': 'quux'}]) - - def testAssertLogsPerLevel(self): - self.checkAssertLogsPerLevel(logging.ERROR) - self.checkAssertLogsPerLevel('ERROR') - - def checkAssertLogsPerLogger(self, logger): - # Check per-logger filtering - with self.assertNoStderr(): - with self.assertLogs(level='DEBUG') as outer_cm: - with self.assertLogs(logger, level='DEBUG') as cm: - log_foo.info("1") - log_foobar.debug("2") - log_quux.warning("3") - self.assertEqual(cm.output, ["INFO:foo:1", "DEBUG:foo.bar:2"]) - self.assertLogRecords(cm.records, - [{'name': 'foo'}, {'name': 'foo.bar'}]) - # The outer catchall caught the quux log - self.assertEqual(outer_cm.output, ["WARNING:quux:3"]) - - def testAssertLogsPerLogger(self): - self.checkAssertLogsPerLogger(logging.getLogger('foo')) - self.checkAssertLogsPerLogger('foo') - - def testAssertLogsFailureNoLogs(self): - # Failure due to no logs - with self.assertNoStderr(): - with self.assertRaises(self.failureException): - with self.assertLogs(): - pass - - def testAssertLogsFailureLevelTooHigh(self): - # Failure due to level too high - with self.assertNoStderr(): - with self.assertRaises(self.failureException): - with self.assertLogs(level='WARNING'): - log_foo.info("1") - - def testAssertLogsFailureMismatchingLogger(self): - # Failure due to mismatching logger (and the logged message is - # passed through) - with self.assertLogs('quux', level='ERROR'): - with self.assertRaises(self.failureException): - with self.assertLogs('foo'): - log_quux.error("1") - - def testDeprecatedMethodNames(self): - """ - Test that the deprecated methods raise a DeprecationWarning. See #9424. - """ - old = ( - (self.failIfEqual, (3, 5)), - (self.assertNotEquals, (3, 5)), - (self.failUnlessEqual, (3, 3)), - (self.assertEquals, (3, 3)), - (self.failUnlessAlmostEqual, (2.0, 2.0)), - (self.assertAlmostEquals, (2.0, 2.0)), - (self.failIfAlmostEqual, (3.0, 5.0)), - (self.assertNotAlmostEquals, (3.0, 5.0)), - (self.failUnless, (True,)), - (self.assert_, (True,)), - (self.failUnlessRaises, (TypeError, lambda _: 3.14 + 'spam')), - (self.failIf, (False,)), - (self.assertDictContainsSubset, (dict(a=1, b=2), dict(a=1, b=2, c=3))), - (self.assertRaisesRegexp, (KeyError, 'foo', lambda: {}['foo'])), - (self.assertRegexpMatches, ('bar', 'bar')), - ) - for meth, args in old: - with self.assertWarns(DeprecationWarning): - meth(*args) - - # disable this test for now. When the version where the fail* methods will - # be removed is decided, re-enable it and update the version - def _testDeprecatedFailMethods(self): - """Test that the deprecated fail* methods get removed in 3.x""" - if sys.version_info[:2] < (3, 3): - return - deprecated_names = [ - 'failIfEqual', 'failUnlessEqual', 'failUnlessAlmostEqual', - 'failIfAlmostEqual', 'failUnless', 'failUnlessRaises', 'failIf', - 'assertDictContainsSubset', - ] - for deprecated_name in deprecated_names: - with self.assertRaises(AttributeError): - getattr(self, deprecated_name) # remove these in 3.x - - def testDeepcopy(self): - # Issue: 5660 - class TestableTest(unittest.TestCase): - def testNothing(self): - pass - - test = TestableTest('testNothing') - - # This shouldn't blow up - deepcopy(test) - - def testPickle(self): - # Issue 10326 - - # Can't use TestCase classes defined in Test class as - # pickle does not work with inner classes - test = unittest.TestCase('run') - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - - # blew up prior to fix - pickled_test = pickle.dumps(test, protocol=protocol) - unpickled_test = pickle.loads(pickled_test) - self.assertEqual(test, unpickled_test) - - # exercise the TestCase instance in a way that will invoke - # the type equality lookup mechanism - unpickled_test.assertEqual(set(), set()) - - def testKeyboardInterrupt(self): - def _raise(self=None): - raise KeyboardInterrupt - def nothing(self): - pass - - class Test1(unittest.TestCase): - test_something = _raise - - class Test2(unittest.TestCase): - setUp = _raise - test_something = nothing - - class Test3(unittest.TestCase): - test_something = nothing - tearDown = _raise - - class Test4(unittest.TestCase): - def test_something(self): - self.addCleanup(_raise) - - for klass in (Test1, Test2, Test3, Test4): - with self.assertRaises(KeyboardInterrupt): - klass('test_something').run() - - def testSkippingEverywhere(self): - def _skip(self=None): - raise unittest.SkipTest('some reason') - def nothing(self): - pass - - class Test1(unittest.TestCase): - test_something = _skip - - class Test2(unittest.TestCase): - setUp = _skip - test_something = nothing - - class Test3(unittest.TestCase): - test_something = nothing - tearDown = _skip - - class Test4(unittest.TestCase): - def test_something(self): - self.addCleanup(_skip) - - for klass in (Test1, Test2, Test3, Test4): - result = unittest.TestResult() - klass('test_something').run(result) - self.assertEqual(len(result.skipped), 1) - self.assertEqual(result.testsRun, 1) - - def testSystemExit(self): - def _raise(self=None): - raise SystemExit - def nothing(self): - pass - - class Test1(unittest.TestCase): - test_something = _raise - - class Test2(unittest.TestCase): - setUp = _raise - test_something = nothing - - class Test3(unittest.TestCase): - test_something = nothing - tearDown = _raise - - class Test4(unittest.TestCase): - def test_something(self): - self.addCleanup(_raise) - - for klass in (Test1, Test2, Test3, Test4): - result = unittest.TestResult() - klass('test_something').run(result) - self.assertEqual(len(result.errors), 1) - self.assertEqual(result.testsRun, 1) - - @support.cpython_only - def testNoCycles(self): - case = unittest.TestCase() - wr = weakref.ref(case) - with support.disable_gc(): - del case - self.assertFalse(wr()) - - def test_no_exception_leak(self): - # Issue #19880: TestCase.run() should not keep a reference - # to the exception - class MyException(Exception): - ninstance = 0 - - def __init__(self): - MyException.ninstance += 1 - Exception.__init__(self) - - def __del__(self): - MyException.ninstance -= 1 - - class TestCase(unittest.TestCase): - def test1(self): - raise MyException() - - @unittest.expectedFailure - def test2(self): - raise MyException() - - for method_name in ('test1', 'test2'): - testcase = TestCase(method_name) - testcase.run() - self.assertEqual(MyException.ninstance, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_discovery.py b/Monika After Story/game/python-packages/unittest/test/test_discovery.py deleted file mode 100644 index 16e081e1fb..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_discovery.py +++ /dev/null @@ -1,879 +0,0 @@ -import os.path -from os.path import abspath -import re -import sys -import types -import pickle -from test import support -import test.test_importlib.util - -import unittest -import unittest.mock -import unittest.test - - -class TestableTestProgram(unittest.TestProgram): - module = None - exit = True - defaultTest = failfast = catchbreak = buffer = None - verbosity = 1 - progName = '' - testRunner = testLoader = None - - def __init__(self): - pass - - -class TestDiscovery(unittest.TestCase): - - # Heavily mocked tests so I can avoid hitting the filesystem - def test_get_name_from_path(self): - loader = unittest.TestLoader() - loader._top_level_dir = '/foo' - name = loader._get_name_from_path('/foo/bar/baz.py') - self.assertEqual(name, 'bar.baz') - - if not __debug__: - # asserts are off - return - - with self.assertRaises(AssertionError): - loader._get_name_from_path('/bar/baz.py') - - def test_find_tests(self): - loader = unittest.TestLoader() - - original_listdir = os.listdir - def restore_listdir(): - os.listdir = original_listdir - original_isfile = os.path.isfile - def restore_isfile(): - os.path.isfile = original_isfile - original_isdir = os.path.isdir - def restore_isdir(): - os.path.isdir = original_isdir - - path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir', - 'test.foo', 'test-not-a-module.py', 'another_dir'], - ['test4.py', 'test3.py', ]] - os.listdir = lambda path: path_lists.pop(0) - self.addCleanup(restore_listdir) - - def isdir(path): - return path.endswith('dir') - os.path.isdir = isdir - self.addCleanup(restore_isdir) - - def isfile(path): - # another_dir is not a package and so shouldn't be recursed into - return not path.endswith('dir') and not 'another_dir' in path - os.path.isfile = isfile - self.addCleanup(restore_isfile) - - loader._get_module_from_name = lambda path: path + ' module' - orig_load_tests = loader.loadTestsFromModule - def loadTestsFromModule(module, pattern=None): - # This is where load_tests is called. - base = orig_load_tests(module, pattern=pattern) - return base + [module + ' tests'] - loader.loadTestsFromModule = loadTestsFromModule - loader.suiteClass = lambda thing: thing - - top_level = os.path.abspath('/foo') - loader._top_level_dir = top_level - suite = list(loader._find_tests(top_level, 'test*.py')) - - # The test suites found should be sorted alphabetically for reliable - # execution order. - expected = [[name + ' module tests'] for name in - ('test1', 'test2', 'test_dir')] - expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in - ('test3', 'test4')]) - self.assertEqual(suite, expected) - - def test_find_tests_socket(self): - # A socket is neither a directory nor a regular file. - # https://bugs.python.org/issue25320 - loader = unittest.TestLoader() - - original_listdir = os.listdir - def restore_listdir(): - os.listdir = original_listdir - original_isfile = os.path.isfile - def restore_isfile(): - os.path.isfile = original_isfile - original_isdir = os.path.isdir - def restore_isdir(): - os.path.isdir = original_isdir - - path_lists = [['socket']] - os.listdir = lambda path: path_lists.pop(0) - self.addCleanup(restore_listdir) - - os.path.isdir = lambda path: False - self.addCleanup(restore_isdir) - - os.path.isfile = lambda path: False - self.addCleanup(restore_isfile) - - loader._get_module_from_name = lambda path: path + ' module' - orig_load_tests = loader.loadTestsFromModule - def loadTestsFromModule(module, pattern=None): - # This is where load_tests is called. - base = orig_load_tests(module, pattern=pattern) - return base + [module + ' tests'] - loader.loadTestsFromModule = loadTestsFromModule - loader.suiteClass = lambda thing: thing - - top_level = os.path.abspath('/foo') - loader._top_level_dir = top_level - suite = list(loader._find_tests(top_level, 'test*.py')) - - self.assertEqual(suite, []) - - def test_find_tests_with_package(self): - loader = unittest.TestLoader() - - original_listdir = os.listdir - def restore_listdir(): - os.listdir = original_listdir - original_isfile = os.path.isfile - def restore_isfile(): - os.path.isfile = original_isfile - original_isdir = os.path.isdir - def restore_isdir(): - os.path.isdir = original_isdir - - directories = ['a_directory', 'test_directory', 'test_directory2'] - path_lists = [directories, [], [], []] - os.listdir = lambda path: path_lists.pop(0) - self.addCleanup(restore_listdir) - - os.path.isdir = lambda path: True - self.addCleanup(restore_isdir) - - os.path.isfile = lambda path: os.path.basename(path) not in directories - self.addCleanup(restore_isfile) - - class Module(object): - paths = [] - load_tests_args = [] - - def __init__(self, path): - self.path = path - self.paths.append(path) - if os.path.basename(path) == 'test_directory': - def load_tests(loader, tests, pattern): - self.load_tests_args.append((loader, tests, pattern)) - return [self.path + ' load_tests'] - self.load_tests = load_tests - - def __eq__(self, other): - return self.path == other.path - - loader._get_module_from_name = lambda name: Module(name) - orig_load_tests = loader.loadTestsFromModule - def loadTestsFromModule(module, pattern=None): - # This is where load_tests is called. - base = orig_load_tests(module, pattern=pattern) - return base + [module.path + ' module tests'] - loader.loadTestsFromModule = loadTestsFromModule - loader.suiteClass = lambda thing: thing - - loader._top_level_dir = '/foo' - # this time no '.py' on the pattern so that it can match - # a test package - suite = list(loader._find_tests('/foo', 'test*')) - - # We should have loaded tests from the a_directory and test_directory2 - # directly and via load_tests for the test_directory package, which - # still calls the baseline module loader. - self.assertEqual(suite, - [['a_directory module tests'], - ['test_directory load_tests', - 'test_directory module tests'], - ['test_directory2 module tests']]) - - - # The test module paths should be sorted for reliable execution order - self.assertEqual(Module.paths, - ['a_directory', 'test_directory', 'test_directory2']) - - # load_tests should have been called once with loader, tests and pattern - # (but there are no tests in our stub module itself, so that is [] at - # the time of call). - self.assertEqual(Module.load_tests_args, - [(loader, [], 'test*')]) - - def test_find_tests_default_calls_package_load_tests(self): - loader = unittest.TestLoader() - - original_listdir = os.listdir - def restore_listdir(): - os.listdir = original_listdir - original_isfile = os.path.isfile - def restore_isfile(): - os.path.isfile = original_isfile - original_isdir = os.path.isdir - def restore_isdir(): - os.path.isdir = original_isdir - - directories = ['a_directory', 'test_directory', 'test_directory2'] - path_lists = [directories, [], [], []] - os.listdir = lambda path: path_lists.pop(0) - self.addCleanup(restore_listdir) - - os.path.isdir = lambda path: True - self.addCleanup(restore_isdir) - - os.path.isfile = lambda path: os.path.basename(path) not in directories - self.addCleanup(restore_isfile) - - class Module(object): - paths = [] - load_tests_args = [] - - def __init__(self, path): - self.path = path - self.paths.append(path) - if os.path.basename(path) == 'test_directory': - def load_tests(loader, tests, pattern): - self.load_tests_args.append((loader, tests, pattern)) - return [self.path + ' load_tests'] - self.load_tests = load_tests - - def __eq__(self, other): - return self.path == other.path - - loader._get_module_from_name = lambda name: Module(name) - orig_load_tests = loader.loadTestsFromModule - def loadTestsFromModule(module, pattern=None): - # This is where load_tests is called. - base = orig_load_tests(module, pattern=pattern) - return base + [module.path + ' module tests'] - loader.loadTestsFromModule = loadTestsFromModule - loader.suiteClass = lambda thing: thing - - loader._top_level_dir = '/foo' - # this time no '.py' on the pattern so that it can match - # a test package - suite = list(loader._find_tests('/foo', 'test*.py')) - - # We should have loaded tests from the a_directory and test_directory2 - # directly and via load_tests for the test_directory package, which - # still calls the baseline module loader. - self.assertEqual(suite, - [['a_directory module tests'], - ['test_directory load_tests', - 'test_directory module tests'], - ['test_directory2 module tests']]) - # The test module paths should be sorted for reliable execution order - self.assertEqual(Module.paths, - ['a_directory', 'test_directory', 'test_directory2']) - - - # load_tests should have been called once with loader, tests and pattern - self.assertEqual(Module.load_tests_args, - [(loader, [], 'test*.py')]) - - def test_find_tests_customize_via_package_pattern(self): - # This test uses the example 'do-nothing' load_tests from - # https://docs.python.org/3/library/unittest.html#load-tests-protocol - # to make sure that that actually works. - # Housekeeping - original_listdir = os.listdir - def restore_listdir(): - os.listdir = original_listdir - self.addCleanup(restore_listdir) - original_isfile = os.path.isfile - def restore_isfile(): - os.path.isfile = original_isfile - self.addCleanup(restore_isfile) - original_isdir = os.path.isdir - def restore_isdir(): - os.path.isdir = original_isdir - self.addCleanup(restore_isdir) - self.addCleanup(sys.path.remove, abspath('/foo')) - - # Test data: we expect the following: - # a listdir to find our package, and isfile and isdir checks on it. - # a module-from-name call to turn that into a module - # followed by load_tests. - # then our load_tests will call discover() which is messy - # but that finally chains into find_tests again for the child dir - - # which is why we don't have an infinite loop. - # We expect to see: - # the module load tests for both package and plain module called, - # and the plain module result nested by the package module load_tests - # indicating that it was processed and could have been mutated. - vfs = {abspath('/foo'): ['my_package'], - abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} - def list_dir(path): - return list(vfs[path]) - os.listdir = list_dir - os.path.isdir = lambda path: not path.endswith('.py') - os.path.isfile = lambda path: path.endswith('.py') - - class Module(object): - paths = [] - load_tests_args = [] - - def __init__(self, path): - self.path = path - self.paths.append(path) - if path.endswith('test_module'): - def load_tests(loader, tests, pattern): - self.load_tests_args.append((loader, tests, pattern)) - return [self.path + ' load_tests'] - else: - def load_tests(loader, tests, pattern): - self.load_tests_args.append((loader, tests, pattern)) - # top level directory cached on loader instance - __file__ = '/foo/my_package/__init__.py' - this_dir = os.path.dirname(__file__) - pkg_tests = loader.discover( - start_dir=this_dir, pattern=pattern) - return [self.path + ' load_tests', tests - ] + pkg_tests - self.load_tests = load_tests - - def __eq__(self, other): - return self.path == other.path - - loader = unittest.TestLoader() - loader._get_module_from_name = lambda name: Module(name) - loader.suiteClass = lambda thing: thing - - loader._top_level_dir = abspath('/foo') - # this time no '.py' on the pattern so that it can match - # a test package - suite = list(loader._find_tests(abspath('/foo'), 'test*.py')) - - # We should have loaded tests from both my_package and - # my_package.test_module, and also run the load_tests hook in both. - # (normally this would be nested TestSuites.) - self.assertEqual(suite, - [['my_package load_tests', [], - ['my_package.test_module load_tests']]]) - # Parents before children. - self.assertEqual(Module.paths, - ['my_package', 'my_package.test_module']) - - # load_tests should have been called twice with loader, tests and pattern - self.assertEqual(Module.load_tests_args, - [(loader, [], 'test*.py'), - (loader, [], 'test*.py')]) - - def test_discover(self): - loader = unittest.TestLoader() - - original_isfile = os.path.isfile - original_isdir = os.path.isdir - def restore_isfile(): - os.path.isfile = original_isfile - - os.path.isfile = lambda path: False - self.addCleanup(restore_isfile) - - orig_sys_path = sys.path[:] - def restore_path(): - sys.path[:] = orig_sys_path - self.addCleanup(restore_path) - - full_path = os.path.abspath(os.path.normpath('/foo')) - with self.assertRaises(ImportError): - loader.discover('/foo/bar', top_level_dir='/foo') - - self.assertEqual(loader._top_level_dir, full_path) - self.assertIn(full_path, sys.path) - - os.path.isfile = lambda path: True - os.path.isdir = lambda path: True - - def restore_isdir(): - os.path.isdir = original_isdir - self.addCleanup(restore_isdir) - - _find_tests_args = [] - def _find_tests(start_dir, pattern, namespace=None): - _find_tests_args.append((start_dir, pattern)) - return ['tests'] - loader._find_tests = _find_tests - loader.suiteClass = str - - suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar') - - top_level_dir = os.path.abspath('/foo/bar') - start_dir = os.path.abspath('/foo/bar/baz') - self.assertEqual(suite, "['tests']") - self.assertEqual(loader._top_level_dir, top_level_dir) - self.assertEqual(_find_tests_args, [(start_dir, 'pattern')]) - self.assertIn(top_level_dir, sys.path) - - def test_discover_start_dir_is_package_calls_package_load_tests(self): - # This test verifies that the package load_tests in a package is indeed - # invoked when the start_dir is a package (and not the top level). - # http://bugs.python.org/issue22457 - - # Test data: we expect the following: - # an isfile to verify the package, then importing and scanning - # as per _find_tests' normal behaviour. - # We expect to see our load_tests hook called once. - vfs = {abspath('/toplevel'): ['startdir'], - abspath('/toplevel/startdir'): ['__init__.py']} - def list_dir(path): - return list(vfs[path]) - self.addCleanup(setattr, os, 'listdir', os.listdir) - os.listdir = list_dir - self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) - os.path.isfile = lambda path: path.endswith('.py') - self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) - os.path.isdir = lambda path: not path.endswith('.py') - self.addCleanup(sys.path.remove, abspath('/toplevel')) - - class Module(object): - paths = [] - load_tests_args = [] - - def __init__(self, path): - self.path = path - - def load_tests(self, loader, tests, pattern): - return ['load_tests called ' + self.path] - - def __eq__(self, other): - return self.path == other.path - - loader = unittest.TestLoader() - loader._get_module_from_name = lambda name: Module(name) - loader.suiteClass = lambda thing: thing - - suite = loader.discover('/toplevel/startdir', top_level_dir='/toplevel') - - # We should have loaded tests from the package __init__. - # (normally this would be nested TestSuites.) - self.assertEqual(suite, - [['load_tests called startdir']]) - - def setup_import_issue_tests(self, fakefile): - listdir = os.listdir - os.listdir = lambda _: [fakefile] - isfile = os.path.isfile - os.path.isfile = lambda _: True - orig_sys_path = sys.path[:] - def restore(): - os.path.isfile = isfile - os.listdir = listdir - sys.path[:] = orig_sys_path - self.addCleanup(restore) - - def setup_import_issue_package_tests(self, vfs): - self.addCleanup(setattr, os, 'listdir', os.listdir) - self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) - self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) - self.addCleanup(sys.path.__setitem__, slice(None), list(sys.path)) - def list_dir(path): - return list(vfs[path]) - os.listdir = list_dir - os.path.isdir = lambda path: not path.endswith('.py') - os.path.isfile = lambda path: path.endswith('.py') - - def test_discover_with_modules_that_fail_to_import(self): - loader = unittest.TestLoader() - - self.setup_import_issue_tests('test_this_does_not_exist.py') - - suite = loader.discover('.') - self.assertIn(os.getcwd(), sys.path) - self.assertEqual(suite.countTestCases(), 1) - # Errors loading the suite are also captured for introspection. - self.assertNotEqual([], loader.errors) - self.assertEqual(1, len(loader.errors)) - error = loader.errors[0] - self.assertTrue( - 'Failed to import test module: test_this_does_not_exist' in error, - 'missing error string in %r' % error) - test = list(list(suite)[0])[0] # extract test from suite - - with self.assertRaises(ImportError): - test.test_this_does_not_exist() - - def test_discover_with_init_modules_that_fail_to_import(self): - vfs = {abspath('/foo'): ['my_package'], - abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} - self.setup_import_issue_package_tests(vfs) - import_calls = [] - def _get_module_from_name(name): - import_calls.append(name) - raise ImportError("Cannot import Name") - loader = unittest.TestLoader() - loader._get_module_from_name = _get_module_from_name - suite = loader.discover(abspath('/foo')) - - self.assertIn(abspath('/foo'), sys.path) - self.assertEqual(suite.countTestCases(), 1) - # Errors loading the suite are also captured for introspection. - self.assertNotEqual([], loader.errors) - self.assertEqual(1, len(loader.errors)) - error = loader.errors[0] - self.assertTrue( - 'Failed to import test module: my_package' in error, - 'missing error string in %r' % error) - test = list(list(suite)[0])[0] # extract test from suite - with self.assertRaises(ImportError): - test.my_package() - self.assertEqual(import_calls, ['my_package']) - - # Check picklability - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickle.loads(pickle.dumps(test, proto)) - - def test_discover_with_module_that_raises_SkipTest_on_import(self): - if not unittest.BaseTestSuite._cleanup: - raise unittest.SkipTest("Suite cleanup is disabled") - - loader = unittest.TestLoader() - - def _get_module_from_name(name): - raise unittest.SkipTest('skipperoo') - loader._get_module_from_name = _get_module_from_name - - self.setup_import_issue_tests('test_skip_dummy.py') - - suite = loader.discover('.') - self.assertEqual(suite.countTestCases(), 1) - - result = unittest.TestResult() - suite.run(result) - self.assertEqual(len(result.skipped), 1) - - # Check picklability - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickle.loads(pickle.dumps(suite, proto)) - - def test_discover_with_init_module_that_raises_SkipTest_on_import(self): - if not unittest.BaseTestSuite._cleanup: - raise unittest.SkipTest("Suite cleanup is disabled") - - vfs = {abspath('/foo'): ['my_package'], - abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} - self.setup_import_issue_package_tests(vfs) - import_calls = [] - def _get_module_from_name(name): - import_calls.append(name) - raise unittest.SkipTest('skipperoo') - loader = unittest.TestLoader() - loader._get_module_from_name = _get_module_from_name - suite = loader.discover(abspath('/foo')) - - self.assertIn(abspath('/foo'), sys.path) - self.assertEqual(suite.countTestCases(), 1) - result = unittest.TestResult() - suite.run(result) - self.assertEqual(len(result.skipped), 1) - self.assertEqual(result.testsRun, 1) - self.assertEqual(import_calls, ['my_package']) - - # Check picklability - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickle.loads(pickle.dumps(suite, proto)) - - def test_command_line_handling_parseArgs(self): - program = TestableTestProgram() - - args = [] - program._do_discovery = args.append - program.parseArgs(['something', 'discover']) - self.assertEqual(args, [[]]) - - args[:] = [] - program.parseArgs(['something', 'discover', 'foo', 'bar']) - self.assertEqual(args, [['foo', 'bar']]) - - def test_command_line_handling_discover_by_default(self): - program = TestableTestProgram() - - args = [] - program._do_discovery = args.append - program.parseArgs(['something']) - self.assertEqual(args, [[]]) - self.assertEqual(program.verbosity, 1) - self.assertIs(program.buffer, False) - self.assertIs(program.catchbreak, False) - self.assertIs(program.failfast, False) - - def test_command_line_handling_discover_by_default_with_options(self): - program = TestableTestProgram() - - args = [] - program._do_discovery = args.append - program.parseArgs(['something', '-v', '-b', '-v', '-c', '-f']) - self.assertEqual(args, [[]]) - self.assertEqual(program.verbosity, 2) - self.assertIs(program.buffer, True) - self.assertIs(program.catchbreak, True) - self.assertIs(program.failfast, True) - - - def test_command_line_handling_do_discovery_too_many_arguments(self): - program = TestableTestProgram() - program.testLoader = None - - with support.captured_stderr() as stderr, \ - self.assertRaises(SystemExit) as cm: - # too many args - program._do_discovery(['one', 'two', 'three', 'four']) - self.assertEqual(cm.exception.args, (2,)) - self.assertIn('usage:', stderr.getvalue()) - - - def test_command_line_handling_do_discovery_uses_default_loader(self): - program = object.__new__(unittest.TestProgram) - program._initArgParsers() - - class Loader(object): - args = [] - def discover(self, start_dir, pattern, top_level_dir): - self.args.append((start_dir, pattern, top_level_dir)) - return 'tests' - - program.testLoader = Loader() - program._do_discovery(['-v']) - self.assertEqual(Loader.args, [('.', 'test*.py', None)]) - - def test_command_line_handling_do_discovery_calls_loader(self): - program = TestableTestProgram() - - class Loader(object): - args = [] - def discover(self, start_dir, pattern, top_level_dir): - self.args.append((start_dir, pattern, top_level_dir)) - return 'tests' - - program._do_discovery(['-v'], Loader=Loader) - self.assertEqual(program.verbosity, 2) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('.', 'test*.py', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['--verbose'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('.', 'test*.py', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery([], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('.', 'test*.py', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['fish'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['fish', 'eggs'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('fish', 'eggs', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['-s', 'fish'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['-t', 'fish'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')]) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['-p', 'fish'], Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('.', 'fish', None)]) - self.assertFalse(program.failfast) - self.assertFalse(program.catchbreak) - - Loader.args = [] - program = TestableTestProgram() - program._do_discovery(['-p', 'eggs', '-s', 'fish', '-v', '-f', '-c'], - Loader=Loader) - self.assertEqual(program.test, 'tests') - self.assertEqual(Loader.args, [('fish', 'eggs', None)]) - self.assertEqual(program.verbosity, 2) - self.assertTrue(program.failfast) - self.assertTrue(program.catchbreak) - - def setup_module_clash(self): - class Module(object): - __file__ = 'bar/foo.py' - sys.modules['foo'] = Module - full_path = os.path.abspath('foo') - original_listdir = os.listdir - original_isfile = os.path.isfile - original_isdir = os.path.isdir - original_realpath = os.path.realpath - - def cleanup(): - os.listdir = original_listdir - os.path.isfile = original_isfile - os.path.isdir = original_isdir - os.path.realpath = original_realpath - del sys.modules['foo'] - if full_path in sys.path: - sys.path.remove(full_path) - self.addCleanup(cleanup) - - def listdir(_): - return ['foo.py'] - def isfile(_): - return True - def isdir(_): - return True - os.listdir = listdir - os.path.isfile = isfile - os.path.isdir = isdir - if os.name == 'nt': - # ntpath.realpath may inject path prefixes when failing to - # resolve real files, so we substitute abspath() here instead. - os.path.realpath = os.path.abspath - return full_path - - def test_detect_module_clash(self): - full_path = self.setup_module_clash() - loader = unittest.TestLoader() - - mod_dir = os.path.abspath('bar') - expected_dir = os.path.abspath('foo') - msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. " - "Is this module globally installed?" % (mod_dir, expected_dir)) - self.assertRaisesRegex( - ImportError, '^%s$' % msg, loader.discover, - start_dir='foo', pattern='foo.py' - ) - self.assertEqual(sys.path[0], full_path) - - def test_module_symlink_ok(self): - full_path = self.setup_module_clash() - - original_realpath = os.path.realpath - - mod_dir = os.path.abspath('bar') - expected_dir = os.path.abspath('foo') - - def cleanup(): - os.path.realpath = original_realpath - self.addCleanup(cleanup) - - def realpath(path): - if path == os.path.join(mod_dir, 'foo.py'): - return os.path.join(expected_dir, 'foo.py') - return path - os.path.realpath = realpath - loader = unittest.TestLoader() - loader.discover(start_dir='foo', pattern='foo.py') - - def test_discovery_from_dotted_path(self): - loader = unittest.TestLoader() - - tests = [self] - expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__)) - - self.wasRun = False - def _find_tests(start_dir, pattern, namespace=None): - self.wasRun = True - self.assertEqual(start_dir, expectedPath) - return tests - loader._find_tests = _find_tests - suite = loader.discover('unittest.test') - self.assertTrue(self.wasRun) - self.assertEqual(suite._tests, tests) - - - def test_discovery_from_dotted_path_builtin_modules(self): - - loader = unittest.TestLoader() - - listdir = os.listdir - os.listdir = lambda _: ['test_this_does_not_exist.py'] - isfile = os.path.isfile - isdir = os.path.isdir - os.path.isdir = lambda _: False - orig_sys_path = sys.path[:] - def restore(): - os.path.isfile = isfile - os.path.isdir = isdir - os.listdir = listdir - sys.path[:] = orig_sys_path - self.addCleanup(restore) - - with self.assertRaises(TypeError) as cm: - loader.discover('sys') - self.assertEqual(str(cm.exception), - 'Can not use builtin modules ' - 'as dotted module names') - - def test_discovery_from_dotted_namespace_packages(self): - loader = unittest.TestLoader() - - package = types.ModuleType('package') - package.__path__ = ['/a', '/b'] - package.__spec__ = types.SimpleNamespace( - loader=None, - submodule_search_locations=['/a', '/b'] - ) - - def _import(packagename, *args, **kwargs): - sys.modules[packagename] = package - return package - - _find_tests_args = [] - def _find_tests(start_dir, pattern, namespace=None): - _find_tests_args.append((start_dir, pattern)) - return ['%s/tests' % start_dir] - - loader._find_tests = _find_tests - loader.suiteClass = list - - with unittest.mock.patch('builtins.__import__', _import): - # Since loader.discover() can modify sys.path, restore it when done. - with support.DirsOnSysPath(): - # Make sure to remove 'package' from sys.modules when done. - with test.test_importlib.util.uncache('package'): - suite = loader.discover('package') - - self.assertEqual(suite, ['/a/tests', '/b/tests']) - - def test_discovery_failed_discovery(self): - loader = unittest.TestLoader() - package = types.ModuleType('package') - - def _import(packagename, *args, **kwargs): - sys.modules[packagename] = package - return package - - with unittest.mock.patch('builtins.__import__', _import): - # Since loader.discover() can modify sys.path, restore it when done. - with support.DirsOnSysPath(): - # Make sure to remove 'package' from sys.modules when done. - with test.test_importlib.util.uncache('package'): - with self.assertRaises(TypeError) as cm: - loader.discover('package') - self.assertEqual(str(cm.exception), - 'don\'t know how to discover from {!r}' - .format(package)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py b/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py deleted file mode 100644 index c5f2bcbe74..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_functiontestcase.py +++ /dev/null @@ -1,148 +0,0 @@ -import unittest - -from unittest.test.support import LoggingResult - - -class Test_FunctionTestCase(unittest.TestCase): - - # "Return the number of tests represented by the this test object. For - # TestCase instances, this will always be 1" - def test_countTestCases(self): - test = unittest.FunctionTestCase(lambda: None) - - self.assertEqual(test.countTestCases(), 1) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if setUp() raises - # an exception. - def test_run_call_order__error_in_setUp(self): - events = [] - result = LoggingResult(events) - - def setUp(): - events.append('setUp') - raise RuntimeError('raised by setUp') - - def test(): - events.append('test') - - def tearDown(): - events.append('tearDown') - - expected = ['startTest', 'setUp', 'addError', 'stopTest'] - unittest.FunctionTestCase(test, setUp, tearDown).run(result) - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if the test raises - # an error (as opposed to a failure). - def test_run_call_order__error_in_test(self): - events = [] - result = LoggingResult(events) - - def setUp(): - events.append('setUp') - - def test(): - events.append('test') - raise RuntimeError('raised by test') - - def tearDown(): - events.append('tearDown') - - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addError', 'stopTest'] - unittest.FunctionTestCase(test, setUp, tearDown).run(result) - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if the test signals - # a failure (as opposed to an error). - def test_run_call_order__failure_in_test(self): - events = [] - result = LoggingResult(events) - - def setUp(): - events.append('setUp') - - def test(): - events.append('test') - self.fail('raised by test') - - def tearDown(): - events.append('tearDown') - - expected = ['startTest', 'setUp', 'test', 'tearDown', - 'addFailure', 'stopTest'] - unittest.FunctionTestCase(test, setUp, tearDown).run(result) - self.assertEqual(events, expected) - - # "When a setUp() method is defined, the test runner will run that method - # prior to each test. Likewise, if a tearDown() method is defined, the - # test runner will invoke that method after each test. In the example, - # setUp() was used to create a fresh sequence for each test." - # - # Make sure the proper call order is maintained, even if tearDown() raises - # an exception. - def test_run_call_order__error_in_tearDown(self): - events = [] - result = LoggingResult(events) - - def setUp(): - events.append('setUp') - - def test(): - events.append('test') - - def tearDown(): - events.append('tearDown') - raise RuntimeError('raised by tearDown') - - expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', - 'stopTest'] - unittest.FunctionTestCase(test, setUp, tearDown).run(result) - self.assertEqual(events, expected) - - # "Return a string identifying the specific test case." - # - # Because of the vague nature of the docs, I'm not going to lock this - # test down too much. Really all that can be asserted is that the id() - # will be a string (either 8-byte or unicode -- again, because the docs - # just say "string") - def test_id(self): - test = unittest.FunctionTestCase(lambda: None) - - self.assertIsInstance(test.id(), str) - - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__no_docstring(self): - test = unittest.FunctionTestCase(lambda: None) - - self.assertEqual(test.shortDescription(), None) - - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__singleline_docstring(self): - desc = "this tests foo" - test = unittest.FunctionTestCase(lambda: None, description=desc) - - self.assertEqual(test.shortDescription(), "this tests foo") - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_loader.py b/Monika After Story/game/python-packages/unittest/test/test_loader.py deleted file mode 100644 index bc54bf0553..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_loader.py +++ /dev/null @@ -1,1595 +0,0 @@ -import functools -import sys -import types -import warnings - -import unittest - -# Decorator used in the deprecation tests to reset the warning registry for -# test isolation and reproducibility. -def warningregistry(func): - def wrapper(*args, **kws): - missing = [] - saved = getattr(warnings, '__warningregistry__', missing).copy() - try: - return func(*args, **kws) - finally: - if saved is missing: - try: - del warnings.__warningregistry__ - except AttributeError: - pass - else: - warnings.__warningregistry__ = saved - return wrapper - - -class Test_TestLoader(unittest.TestCase): - - ### Basic object tests - ################################################################ - - def test___init__(self): - loader = unittest.TestLoader() - self.assertEqual([], loader.errors) - - ### Tests for TestLoader.loadTestsFromTestCase - ################################################################ - - # "Return a suite of all test cases contained in the TestCase-derived - # class testCaseClass" - def test_loadTestsFromTestCase(self): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - - tests = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) - - loader = unittest.TestLoader() - self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) - - # "Return a suite of all test cases contained in the TestCase-derived - # class testCaseClass" - # - # Make sure it does the right thing even if no tests were found - def test_loadTestsFromTestCase__no_matches(self): - class Foo(unittest.TestCase): - def foo_bar(self): pass - - empty_suite = unittest.TestSuite() - - loader = unittest.TestLoader() - self.assertEqual(loader.loadTestsFromTestCase(Foo), empty_suite) - - # "Return a suite of all test cases contained in the TestCase-derived - # class testCaseClass" - # - # What happens if loadTestsFromTestCase() is given an object - # that isn't a subclass of TestCase? Specifically, what happens - # if testCaseClass is a subclass of TestSuite? - # - # This is checked for specifically in the code, so we better add a - # test for it. - def test_loadTestsFromTestCase__TestSuite_subclass(self): - class NotATestCase(unittest.TestSuite): - pass - - loader = unittest.TestLoader() - try: - loader.loadTestsFromTestCase(NotATestCase) - except TypeError: - pass - else: - self.fail('Should raise TypeError') - - # "Return a suite of all test cases contained in the TestCase-derived - # class testCaseClass" - # - # Make sure loadTestsFromTestCase() picks up the default test method - # name (as specified by TestCase), even though the method name does - # not match the default TestLoader.testMethodPrefix string - def test_loadTestsFromTestCase__default_method_name(self): - class Foo(unittest.TestCase): - def runTest(self): - pass - - loader = unittest.TestLoader() - # This has to be false for the test to succeed - self.assertFalse('runTest'.startswith(loader.testMethodPrefix)) - - suite = loader.loadTestsFromTestCase(Foo) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [Foo('runTest')]) - - ################################################################ - ### /Tests for TestLoader.loadTestsFromTestCase - - ### Tests for TestLoader.loadTestsFromModule - ################################################################ - - # "This method searches `module` for classes derived from TestCase" - def test_loadTestsFromModule__TestCase_subclass(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m) - self.assertIsInstance(suite, loader.suiteClass) - - expected = [loader.suiteClass([MyTestCase('test')])] - self.assertEqual(list(suite), expected) - - # "This method searches `module` for classes derived from TestCase" - # - # What happens if no tests are found (no TestCase instances)? - def test_loadTestsFromModule__no_TestCase_instances(self): - m = types.ModuleType('m') - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), []) - - # "This method searches `module` for classes derived from TestCase" - # - # What happens if no tests are found (TestCases instances, but no tests)? - def test_loadTestsFromModule__no_TestCase_tests(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m) - self.assertIsInstance(suite, loader.suiteClass) - - self.assertEqual(list(suite), [loader.suiteClass()]) - - # "This method searches `module` for classes derived from TestCase"s - # - # What happens if loadTestsFromModule() is given something other - # than a module? - # - # XXX Currently, it succeeds anyway. This flexibility - # should either be documented or loadTestsFromModule() should - # raise a TypeError - # - # XXX Certain people are using this behaviour. We'll add a test for it - def test_loadTestsFromModule__not_a_module(self): - class MyTestCase(unittest.TestCase): - def test(self): - pass - - class NotAModule(object): - test_2 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(NotAModule) - - reference = [unittest.TestSuite([MyTestCase('test')])] - self.assertEqual(list(suite), reference) - - - # Check that loadTestsFromModule honors (or not) a module - # with a load_tests function. - @warningregistry - def test_loadTestsFromModule__load_tests(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m) - self.assertIsInstance(suite, unittest.TestSuite) - self.assertEqual(load_tests_args, [loader, suite, None]) - # With Python 3.5, the undocumented and unofficial use_load_tests is - # ignored (and deprecated). - load_tests_args = [] - with warnings.catch_warnings(record=False): - warnings.simplefilter('ignore') - suite = loader.loadTestsFromModule(m, use_load_tests=False) - self.assertEqual(load_tests_args, [loader, suite, None]) - - @warningregistry - def test_loadTestsFromModule__use_load_tests_deprecated_positional(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - # The method still works. - loader = unittest.TestLoader() - # use_load_tests=True as a positional argument. - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - suite = loader.loadTestsFromModule(m, False) - self.assertIsInstance(suite, unittest.TestSuite) - # load_tests was still called because use_load_tests is deprecated - # and ignored. - self.assertEqual(load_tests_args, [loader, suite, None]) - # We got a warning. - self.assertIs(w[-1].category, DeprecationWarning) - self.assertEqual(str(w[-1].message), - 'use_load_tests is deprecated and ignored') - - @warningregistry - def test_loadTestsFromModule__use_load_tests_deprecated_keyword(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - # The method still works. - loader = unittest.TestLoader() - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - suite = loader.loadTestsFromModule(m, use_load_tests=False) - self.assertIsInstance(suite, unittest.TestSuite) - # load_tests was still called because use_load_tests is deprecated - # and ignored. - self.assertEqual(load_tests_args, [loader, suite, None]) - # We got a warning. - self.assertIs(w[-1].category, DeprecationWarning) - self.assertEqual(str(w[-1].message), - 'use_load_tests is deprecated and ignored') - - @warningregistry - def test_loadTestsFromModule__too_many_positional_args(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - loader = unittest.TestLoader() - with self.assertRaises(TypeError) as cm, \ - warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - loader.loadTestsFromModule(m, False, 'testme.*') - # We still got the deprecation warning. - self.assertIs(w[-1].category, DeprecationWarning) - self.assertEqual(str(w[-1].message), - 'use_load_tests is deprecated and ignored') - # We also got a TypeError for too many positional arguments. - self.assertEqual(type(cm.exception), TypeError) - self.assertEqual( - str(cm.exception), - 'loadTestsFromModule() takes 1 positional argument but 3 were given') - - @warningregistry - def test_loadTestsFromModule__use_load_tests_other_bad_keyword(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - loader = unittest.TestLoader() - with warnings.catch_warnings(): - warnings.simplefilter('ignore') - with self.assertRaises(TypeError) as cm: - loader.loadTestsFromModule( - m, use_load_tests=False, very_bad=True, worse=False) - self.assertEqual(type(cm.exception), TypeError) - # The error message names the first bad argument alphabetically, - # however use_load_tests (which sorts first) is ignored. - self.assertEqual( - str(cm.exception), - "loadTestsFromModule() got an unexpected keyword argument 'very_bad'") - - def test_loadTestsFromModule__pattern(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - load_tests_args = [] - def load_tests(loader, tests, pattern): - self.assertIsInstance(tests, unittest.TestSuite) - load_tests_args.extend((loader, tests, pattern)) - return tests - m.load_tests = load_tests - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m, pattern='testme.*') - self.assertIsInstance(suite, unittest.TestSuite) - self.assertEqual(load_tests_args, [loader, suite, 'testme.*']) - - def test_loadTestsFromModule__faulty_load_tests(self): - m = types.ModuleType('m') - - def load_tests(loader, tests, pattern): - raise TypeError('some failure') - m.load_tests = load_tests - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(m) - self.assertIsInstance(suite, unittest.TestSuite) - self.assertEqual(suite.countTestCases(), 1) - # Errors loading the suite are also captured for introspection. - self.assertNotEqual([], loader.errors) - self.assertEqual(1, len(loader.errors)) - error = loader.errors[0] - self.assertTrue( - 'Failed to call load_tests:' in error, - 'missing error string in %r' % error) - test = list(suite)[0] - - self.assertRaisesRegex(TypeError, "some failure", test.m) - - ################################################################ - ### /Tests for TestLoader.loadTestsFromModule() - - ### Tests for TestLoader.loadTestsFromName() - ################################################################ - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # Is ValueError raised in response to an empty name? - def test_loadTestsFromName__empty_name(self): - loader = unittest.TestLoader() - - try: - loader.loadTestsFromName('') - except ValueError as e: - self.assertEqual(str(e), "Empty module name") - else: - self.fail("TestLoader.loadTestsFromName failed to raise ValueError") - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when the name contains invalid characters? - def test_loadTestsFromName__malformed_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('abc () //') - error, test = self.check_deferred_error(loader, suite) - expected = "Failed to import test module: abc () //" - expected_regex = r"Failed to import test module: abc \(\) //" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex( - ImportError, expected_regex, getattr(test, 'abc () //')) - - # "The specifier name is a ``dotted name'' that may resolve ... to a - # module" - # - # What happens when a module by that name can't be found? - def test_loadTestsFromName__unknown_module_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('sdasfasfasdf') - expected = "No module named 'sdasfasfasdf'" - error, test = self.check_deferred_error(loader, suite) - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when the module is found, but the attribute isn't? - def test_loadTestsFromName__unknown_attr_name_on_module(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('unittest.loader.sdasfasfasdf') - expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" - error, test = self.check_deferred_error(loader, suite) - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when the module is found, but the attribute isn't? - def test_loadTestsFromName__unknown_attr_name_on_package(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('unittest.sdasfasfasdf') - expected = "No module named 'unittest.sdasfasfasdf'" - error, test = self.check_deferred_error(loader, suite) - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when we provide the module, but the attribute can't be - # found? - def test_loadTestsFromName__relative_unknown_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('sdasfasfasdf', unittest) - expected = "module 'unittest' has no attribute 'sdasfasfasdf'" - error, test = self.check_deferred_error(loader, suite) - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # Does loadTestsFromName raise ValueError when passed an empty - # name relative to a provided module? - # - # XXX Should probably raise a ValueError instead of an AttributeError - def test_loadTestsFromName__relative_empty_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromName('', unittest) - error, test = self.check_deferred_error(loader, suite) - expected = "has no attribute ''" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, getattr(test, '')) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # What happens when an impossible name is given, relative to the provided - # `module`? - def test_loadTestsFromName__relative_malformed_name(self): - loader = unittest.TestLoader() - - # XXX Should this raise AttributeError or ValueError? - suite = loader.loadTestsFromName('abc () //', unittest) - error, test = self.check_deferred_error(loader, suite) - expected = "module 'unittest' has no attribute 'abc () //'" - expected_regex = r"module 'unittest' has no attribute 'abc \(\) //'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex( - AttributeError, expected_regex, getattr(test, 'abc () //')) - - # "The method optionally resolves name relative to the given module" - # - # Does loadTestsFromName raise TypeError when the `module` argument - # isn't a module object? - # - # XXX Accepts the not-a-module object, ignoring the object's type - # This should raise an exception or the method name should be changed - # - # XXX Some people are relying on this, so keep it for now - def test_loadTestsFromName__relative_not_a_module(self): - class MyTestCase(unittest.TestCase): - def test(self): - pass - - class NotAModule(object): - test_2 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('test_2', NotAModule) - - reference = [MyTestCase('test')] - self.assertEqual(list(suite), reference) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # Does it raise an exception if the name resolves to an invalid - # object? - def test_loadTestsFromName__relative_bad_object(self): - m = types.ModuleType('m') - m.testcase_1 = object() - - loader = unittest.TestLoader() - try: - loader.loadTestsFromName('testcase_1', m) - except TypeError: - pass - else: - self.fail("Should have raised TypeError") - - # "The specifier name is a ``dotted name'' that may - # resolve either to ... a test case class" - def test_loadTestsFromName__relative_TestCase_subclass(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('testcase_1', m) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [MyTestCase('test')]) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - def test_loadTestsFromName__relative_TestSuite(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testsuite = unittest.TestSuite([MyTestCase('test')]) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('testsuite', m) - self.assertIsInstance(suite, loader.suiteClass) - - self.assertEqual(list(suite), [MyTestCase('test')]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a test method within a test case class" - def test_loadTestsFromName__relative_testmethod(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('testcase_1.test', m) - self.assertIsInstance(suite, loader.suiteClass) - - self.assertEqual(list(suite), [MyTestCase('test')]) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # Does loadTestsFromName() raise the proper exception when trying to - # resolve "a test method within a test case class" that doesn't exist - # for the given name (relative to a provided module)? - def test_loadTestsFromName__relative_invalid_testmethod(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('testcase_1.testfoo', m) - expected = "type object 'MyTestCase' has no attribute 'testfoo'" - error, test = self.check_deferred_error(loader, suite) - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.testfoo) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a ... TestSuite instance" - def test_loadTestsFromName__callable__TestSuite(self): - m = types.ModuleType('m') - testcase_1 = unittest.FunctionTestCase(lambda: None) - testcase_2 = unittest.FunctionTestCase(lambda: None) - def return_TestSuite(): - return unittest.TestSuite([testcase_1, testcase_2]) - m.return_TestSuite = return_TestSuite - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('return_TestSuite', m) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [testcase_1, testcase_2]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase ... instance" - def test_loadTestsFromName__callable__TestCase_instance(self): - m = types.ModuleType('m') - testcase_1 = unittest.FunctionTestCase(lambda: None) - def return_TestCase(): - return testcase_1 - m.return_TestCase = return_TestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromName('return_TestCase', m) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [testcase_1]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase ... instance" - #***************************************************************** - #Override the suiteClass attribute to ensure that the suiteClass - #attribute is used - def test_loadTestsFromName__callable__TestCase_instance_ProperSuiteClass(self): - class SubTestSuite(unittest.TestSuite): - pass - m = types.ModuleType('m') - testcase_1 = unittest.FunctionTestCase(lambda: None) - def return_TestCase(): - return testcase_1 - m.return_TestCase = return_TestCase - - loader = unittest.TestLoader() - loader.suiteClass = SubTestSuite - suite = loader.loadTestsFromName('return_TestCase', m) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [testcase_1]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a test method within a test case class" - #***************************************************************** - #Override the suiteClass attribute to ensure that the suiteClass - #attribute is used - def test_loadTestsFromName__relative_testmethod_ProperSuiteClass(self): - class SubTestSuite(unittest.TestSuite): - pass - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - loader.suiteClass=SubTestSuite - suite = loader.loadTestsFromName('testcase_1.test', m) - self.assertIsInstance(suite, loader.suiteClass) - - self.assertEqual(list(suite), [MyTestCase('test')]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase or TestSuite instance" - # - # What happens if the callable returns something else? - def test_loadTestsFromName__callable__wrong_type(self): - m = types.ModuleType('m') - def return_wrong(): - return 6 - m.return_wrong = return_wrong - - loader = unittest.TestLoader() - try: - suite = loader.loadTestsFromName('return_wrong', m) - except TypeError: - pass - else: - self.fail("TestLoader.loadTestsFromName failed to raise TypeError") - - # "The specifier can refer to modules and packages which have not been - # imported; they will be imported as a side-effect" - def test_loadTestsFromName__module_not_loaded(self): - # We're going to try to load this module as a side-effect, so it - # better not be loaded before we try. - # - module_name = 'unittest.test.dummy' - sys.modules.pop(module_name, None) - - loader = unittest.TestLoader() - try: - suite = loader.loadTestsFromName(module_name) - - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), []) - - # module should now be loaded, thanks to loadTestsFromName() - self.assertIn(module_name, sys.modules) - finally: - if module_name in sys.modules: - del sys.modules[module_name] - - ################################################################ - ### Tests for TestLoader.loadTestsFromName() - - ### Tests for TestLoader.loadTestsFromNames() - ################################################################ - - def check_deferred_error(self, loader, suite): - """Helper function for checking that errors in loading are reported. - - :param loader: A loader with some errors. - :param suite: A suite that should have a late bound error. - :return: The first error message from the loader and the test object - from the suite. - """ - self.assertIsInstance(suite, unittest.TestSuite) - self.assertEqual(suite.countTestCases(), 1) - # Errors loading the suite are also captured for introspection. - self.assertNotEqual([], loader.errors) - self.assertEqual(1, len(loader.errors)) - error = loader.errors[0] - test = list(suite)[0] - return error, test - - # "Similar to loadTestsFromName(), but takes a sequence of names rather - # than a single name." - # - # What happens if that sequence of names is empty? - def test_loadTestsFromNames__empty_name_list(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames([]) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), []) - - # "Similar to loadTestsFromName(), but takes a sequence of names rather - # than a single name." - # ... - # "The method optionally resolves name relative to the given module" - # - # What happens if that sequence of names is empty? - # - # XXX Should this raise a ValueError or just return an empty TestSuite? - def test_loadTestsFromNames__relative_empty_name_list(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames([], unittest) - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), []) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # Is ValueError raised in response to an empty name? - def test_loadTestsFromNames__empty_name(self): - loader = unittest.TestLoader() - - try: - loader.loadTestsFromNames(['']) - except ValueError as e: - self.assertEqual(str(e), "Empty module name") - else: - self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when presented with an impossible module name? - def test_loadTestsFromNames__malformed_name(self): - loader = unittest.TestLoader() - - # XXX Should this raise ValueError or ImportError? - suite = loader.loadTestsFromNames(['abc () //']) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "Failed to import test module: abc () //" - expected_regex = r"Failed to import test module: abc \(\) //" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex( - ImportError, expected_regex, getattr(test, 'abc () //')) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when no module can be found for the given name? - def test_loadTestsFromNames__unknown_module_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames(['sdasfasfasdf']) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "Failed to import test module: sdasfasfasdf" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # What happens when the module can be found, but not the attribute? - def test_loadTestsFromNames__unknown_attr_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames( - ['unittest.loader.sdasfasfasdf', 'unittest.test.dummy']) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # What happens when given an unknown attribute on a specified `module` - # argument? - def test_loadTestsFromNames__unknown_name_relative_1(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames(['sdasfasfasdf'], unittest) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "module 'unittest' has no attribute 'sdasfasfasdf'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # Do unknown attributes (relative to a provided module) still raise an - # exception even in the presence of valid attribute names? - def test_loadTestsFromNames__unknown_name_relative_2(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest) - error, test = self.check_deferred_error(loader, list(suite)[1]) - expected = "module 'unittest' has no attribute 'sdasfasfasdf'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # What happens when faced with the empty string? - # - # XXX This currently raises AttributeError, though ValueError is probably - # more appropriate - def test_loadTestsFromNames__relative_empty_name(self): - loader = unittest.TestLoader() - - suite = loader.loadTestsFromNames([''], unittest) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "has no attribute ''" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, getattr(test, '')) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # ... - # "The method optionally resolves name relative to the given module" - # - # What happens when presented with an impossible attribute name? - def test_loadTestsFromNames__relative_malformed_name(self): - loader = unittest.TestLoader() - - # XXX Should this raise AttributeError or ValueError? - suite = loader.loadTestsFromNames(['abc () //'], unittest) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "module 'unittest' has no attribute 'abc () //'" - expected_regex = r"module 'unittest' has no attribute 'abc \(\) //'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex( - AttributeError, expected_regex, getattr(test, 'abc () //')) - - # "The method optionally resolves name relative to the given module" - # - # Does loadTestsFromNames() make sure the provided `module` is in fact - # a module? - # - # XXX This validation is currently not done. This flexibility should - # either be documented or a TypeError should be raised. - def test_loadTestsFromNames__relative_not_a_module(self): - class MyTestCase(unittest.TestCase): - def test(self): - pass - - class NotAModule(object): - test_2 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['test_2'], NotAModule) - - reference = [unittest.TestSuite([MyTestCase('test')])] - self.assertEqual(list(suite), reference) - - # "The specifier name is a ``dotted name'' that may resolve either to - # a module, a test case class, a TestSuite instance, a test method - # within a test case class, or a callable object which returns a - # TestCase or TestSuite instance." - # - # Does it raise an exception if the name resolves to an invalid - # object? - def test_loadTestsFromNames__relative_bad_object(self): - m = types.ModuleType('m') - m.testcase_1 = object() - - loader = unittest.TestLoader() - try: - loader.loadTestsFromNames(['testcase_1'], m) - except TypeError: - pass - else: - self.fail("Should have raised TypeError") - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a test case class" - def test_loadTestsFromNames__relative_TestCase_subclass(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['testcase_1'], m) - self.assertIsInstance(suite, loader.suiteClass) - - expected = loader.suiteClass([MyTestCase('test')]) - self.assertEqual(list(suite), [expected]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a TestSuite instance" - def test_loadTestsFromNames__relative_TestSuite(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testsuite = unittest.TestSuite([MyTestCase('test')]) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['testsuite'], m) - self.assertIsInstance(suite, loader.suiteClass) - - self.assertEqual(list(suite), [m.testsuite]) - - # "The specifier name is a ``dotted name'' that may resolve ... to ... a - # test method within a test case class" - def test_loadTestsFromNames__relative_testmethod(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['testcase_1.test'], m) - self.assertIsInstance(suite, loader.suiteClass) - - ref_suite = unittest.TestSuite([MyTestCase('test')]) - self.assertEqual(list(suite), [ref_suite]) - - # #14971: Make sure the dotted name resolution works even if the actual - # function doesn't have the same name as is used to find it. - def test_loadTestsFromName__function_with_different_name_than_method(self): - # lambdas have the name ''. - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - test = lambda: 1 - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['testcase_1.test'], m) - self.assertIsInstance(suite, loader.suiteClass) - - ref_suite = unittest.TestSuite([MyTestCase('test')]) - self.assertEqual(list(suite), [ref_suite]) - - # "The specifier name is a ``dotted name'' that may resolve ... to ... a - # test method within a test case class" - # - # Does the method gracefully handle names that initially look like they - # resolve to "a test method within a test case class" but don't? - def test_loadTestsFromNames__relative_invalid_testmethod(self): - m = types.ModuleType('m') - class MyTestCase(unittest.TestCase): - def test(self): - pass - m.testcase_1 = MyTestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['testcase_1.testfoo'], m) - error, test = self.check_deferred_error(loader, list(suite)[0]) - expected = "type object 'MyTestCase' has no attribute 'testfoo'" - self.assertIn( - expected, error, - 'missing error string in %r' % error) - self.assertRaisesRegex(AttributeError, expected, test.testfoo) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a ... TestSuite instance" - def test_loadTestsFromNames__callable__TestSuite(self): - m = types.ModuleType('m') - testcase_1 = unittest.FunctionTestCase(lambda: None) - testcase_2 = unittest.FunctionTestCase(lambda: None) - def return_TestSuite(): - return unittest.TestSuite([testcase_1, testcase_2]) - m.return_TestSuite = return_TestSuite - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['return_TestSuite'], m) - self.assertIsInstance(suite, loader.suiteClass) - - expected = unittest.TestSuite([testcase_1, testcase_2]) - self.assertEqual(list(suite), [expected]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase ... instance" - def test_loadTestsFromNames__callable__TestCase_instance(self): - m = types.ModuleType('m') - testcase_1 = unittest.FunctionTestCase(lambda: None) - def return_TestCase(): - return testcase_1 - m.return_TestCase = return_TestCase - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['return_TestCase'], m) - self.assertIsInstance(suite, loader.suiteClass) - - ref_suite = unittest.TestSuite([testcase_1]) - self.assertEqual(list(suite), [ref_suite]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase or TestSuite instance" - # - # Are staticmethods handled correctly? - def test_loadTestsFromNames__callable__call_staticmethod(self): - m = types.ModuleType('m') - class Test1(unittest.TestCase): - def test(self): - pass - - testcase_1 = Test1('test') - class Foo(unittest.TestCase): - @staticmethod - def foo(): - return testcase_1 - m.Foo = Foo - - loader = unittest.TestLoader() - suite = loader.loadTestsFromNames(['Foo.foo'], m) - self.assertIsInstance(suite, loader.suiteClass) - - ref_suite = unittest.TestSuite([testcase_1]) - self.assertEqual(list(suite), [ref_suite]) - - # "The specifier name is a ``dotted name'' that may resolve ... to - # ... a callable object which returns a TestCase or TestSuite instance" - # - # What happens when the callable returns something else? - def test_loadTestsFromNames__callable__wrong_type(self): - m = types.ModuleType('m') - def return_wrong(): - return 6 - m.return_wrong = return_wrong - - loader = unittest.TestLoader() - try: - suite = loader.loadTestsFromNames(['return_wrong'], m) - except TypeError: - pass - else: - self.fail("TestLoader.loadTestsFromNames failed to raise TypeError") - - # "The specifier can refer to modules and packages which have not been - # imported; they will be imported as a side-effect" - def test_loadTestsFromNames__module_not_loaded(self): - # We're going to try to load this module as a side-effect, so it - # better not be loaded before we try. - # - module_name = 'unittest.test.dummy' - sys.modules.pop(module_name, None) - - loader = unittest.TestLoader() - try: - suite = loader.loadTestsFromNames([module_name]) - - self.assertIsInstance(suite, loader.suiteClass) - self.assertEqual(list(suite), [unittest.TestSuite()]) - - # module should now be loaded, thanks to loadTestsFromName() - self.assertIn(module_name, sys.modules) - finally: - if module_name in sys.modules: - del sys.modules[module_name] - - ################################################################ - ### /Tests for TestLoader.loadTestsFromNames() - - ### Tests for TestLoader.getTestCaseNames() - ################################################################ - - # "Return a sorted sequence of method names found within testCaseClass" - # - # Test.foobar is defined to make sure getTestCaseNames() respects - # loader.testMethodPrefix - def test_getTestCaseNames(self): - class Test(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foobar(self): pass - - loader = unittest.TestLoader() - - self.assertEqual(loader.getTestCaseNames(Test), ['test_1', 'test_2']) - - # "Return a sorted sequence of method names found within testCaseClass" - # - # Does getTestCaseNames() behave appropriately if no tests are found? - def test_getTestCaseNames__no_tests(self): - class Test(unittest.TestCase): - def foobar(self): pass - - loader = unittest.TestLoader() - - self.assertEqual(loader.getTestCaseNames(Test), []) - - # "Return a sorted sequence of method names found within testCaseClass" - # - # Are not-TestCases handled gracefully? - # - # XXX This should raise a TypeError, not return a list - # - # XXX It's too late in the 2.5 release cycle to fix this, but it should - # probably be revisited for 2.6 - def test_getTestCaseNames__not_a_TestCase(self): - class BadCase(int): - def test_foo(self): - pass - - loader = unittest.TestLoader() - names = loader.getTestCaseNames(BadCase) - - self.assertEqual(names, ['test_foo']) - - # "Return a sorted sequence of method names found within testCaseClass" - # - # Make sure inherited names are handled. - # - # TestP.foobar is defined to make sure getTestCaseNames() respects - # loader.testMethodPrefix - def test_getTestCaseNames__inheritance(self): - class TestP(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foobar(self): pass - - class TestC(TestP): - def test_1(self): pass - def test_3(self): pass - - loader = unittest.TestLoader() - - names = ['test_1', 'test_2', 'test_3'] - self.assertEqual(loader.getTestCaseNames(TestC), names) - - # "Return a sorted sequence of method names found within testCaseClass" - # - # If TestLoader.testNamePatterns is set, only tests that match one of these - # patterns should be included. - def test_getTestCaseNames__testNamePatterns(self): - class MyTest(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foobar(self): pass - - loader = unittest.TestLoader() - - loader.testNamePatterns = [] - self.assertEqual(loader.getTestCaseNames(MyTest), []) - - loader.testNamePatterns = ['*1'] - self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1']) - - loader.testNamePatterns = ['*1', '*2'] - self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1', 'test_2']) - - loader.testNamePatterns = ['*My*'] - self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1', 'test_2']) - - loader.testNamePatterns = ['*my*'] - self.assertEqual(loader.getTestCaseNames(MyTest), []) - - # "Return a sorted sequence of method names found within testCaseClass" - # - # If TestLoader.testNamePatterns is set, only tests that match one of these - # patterns should be included. - # - # For backwards compatibility reasons (see bpo-32071), the check may only - # touch a TestCase's attribute if it starts with the test method prefix. - def test_getTestCaseNames__testNamePatterns__attribute_access_regression(self): - class Trap: - def __get__(*ignored): - self.fail('Non-test attribute accessed') - - class MyTest(unittest.TestCase): - def test_1(self): pass - foobar = Trap() - - loader = unittest.TestLoader() - self.assertEqual(loader.getTestCaseNames(MyTest), ['test_1']) - - loader = unittest.TestLoader() - loader.testNamePatterns = [] - self.assertEqual(loader.getTestCaseNames(MyTest), []) - - ################################################################ - ### /Tests for TestLoader.getTestCaseNames() - - ### Tests for TestLoader.testMethodPrefix - ################################################################ - - # "String giving the prefix of method names which will be interpreted as - # test methods" - # - # Implicit in the documentation is that testMethodPrefix is respected by - # all loadTestsFrom* methods. - def test_testMethodPrefix__loadTestsFromTestCase(self): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - - tests_1 = unittest.TestSuite([Foo('foo_bar')]) - tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) - - loader = unittest.TestLoader() - loader.testMethodPrefix = 'foo' - self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_1) - - loader.testMethodPrefix = 'test' - self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_2) - - # "String giving the prefix of method names which will be interpreted as - # test methods" - # - # Implicit in the documentation is that testMethodPrefix is respected by - # all loadTestsFrom* methods. - def test_testMethodPrefix__loadTestsFromModule(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests_1 = [unittest.TestSuite([Foo('foo_bar')])] - tests_2 = [unittest.TestSuite([Foo('test_1'), Foo('test_2')])] - - loader = unittest.TestLoader() - loader.testMethodPrefix = 'foo' - self.assertEqual(list(loader.loadTestsFromModule(m)), tests_1) - - loader.testMethodPrefix = 'test' - self.assertEqual(list(loader.loadTestsFromModule(m)), tests_2) - - # "String giving the prefix of method names which will be interpreted as - # test methods" - # - # Implicit in the documentation is that testMethodPrefix is respected by - # all loadTestsFrom* methods. - def test_testMethodPrefix__loadTestsFromName(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests_1 = unittest.TestSuite([Foo('foo_bar')]) - tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) - - loader = unittest.TestLoader() - loader.testMethodPrefix = 'foo' - self.assertEqual(loader.loadTestsFromName('Foo', m), tests_1) - - loader.testMethodPrefix = 'test' - self.assertEqual(loader.loadTestsFromName('Foo', m), tests_2) - - # "String giving the prefix of method names which will be interpreted as - # test methods" - # - # Implicit in the documentation is that testMethodPrefix is respected by - # all loadTestsFrom* methods. - def test_testMethodPrefix__loadTestsFromNames(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests_1 = unittest.TestSuite([unittest.TestSuite([Foo('foo_bar')])]) - tests_2 = unittest.TestSuite([Foo('test_1'), Foo('test_2')]) - tests_2 = unittest.TestSuite([tests_2]) - - loader = unittest.TestLoader() - loader.testMethodPrefix = 'foo' - self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_1) - - loader.testMethodPrefix = 'test' - self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_2) - - # "The default value is 'test'" - def test_testMethodPrefix__default_value(self): - loader = unittest.TestLoader() - self.assertEqual(loader.testMethodPrefix, 'test') - - ################################################################ - ### /Tests for TestLoader.testMethodPrefix - - ### Tests for TestLoader.sortTestMethodsUsing - ################################################################ - - # "Function to be used to compare method names when sorting them in - # getTestCaseNames() and all the loadTestsFromX() methods" - def test_sortTestMethodsUsing__loadTestsFromTestCase(self): - def reversed_cmp(x, y): - return -((x > y) - (x < y)) - - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = reversed_cmp - - tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) - self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) - - # "Function to be used to compare method names when sorting them in - # getTestCaseNames() and all the loadTestsFromX() methods" - def test_sortTestMethodsUsing__loadTestsFromModule(self): - def reversed_cmp(x, y): - return -((x > y) - (x < y)) - - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - m.Foo = Foo - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = reversed_cmp - - tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] - self.assertEqual(list(loader.loadTestsFromModule(m)), tests) - - # "Function to be used to compare method names when sorting them in - # getTestCaseNames() and all the loadTestsFromX() methods" - def test_sortTestMethodsUsing__loadTestsFromName(self): - def reversed_cmp(x, y): - return -((x > y) - (x < y)) - - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - m.Foo = Foo - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = reversed_cmp - - tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) - self.assertEqual(loader.loadTestsFromName('Foo', m), tests) - - # "Function to be used to compare method names when sorting them in - # getTestCaseNames() and all the loadTestsFromX() methods" - def test_sortTestMethodsUsing__loadTestsFromNames(self): - def reversed_cmp(x, y): - return -((x > y) - (x < y)) - - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - m.Foo = Foo - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = reversed_cmp - - tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] - self.assertEqual(list(loader.loadTestsFromNames(['Foo'], m)), tests) - - # "Function to be used to compare method names when sorting them in - # getTestCaseNames()" - # - # Does it actually affect getTestCaseNames()? - def test_sortTestMethodsUsing__getTestCaseNames(self): - def reversed_cmp(x, y): - return -((x > y) - (x < y)) - - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = reversed_cmp - - test_names = ['test_2', 'test_1'] - self.assertEqual(loader.getTestCaseNames(Foo), test_names) - - # "The default value is the built-in cmp() function" - # Since cmp is now defunct, we simply verify that the results - # occur in the same order as they would with the default sort. - def test_sortTestMethodsUsing__default_value(self): - loader = unittest.TestLoader() - - class Foo(unittest.TestCase): - def test_2(self): pass - def test_3(self): pass - def test_1(self): pass - - test_names = ['test_2', 'test_3', 'test_1'] - self.assertEqual(loader.getTestCaseNames(Foo), sorted(test_names)) - - - # "it can be set to None to disable the sort." - # - # XXX How is this different from reassigning cmp? Are the tests returned - # in a random order or something? This behaviour should die - def test_sortTestMethodsUsing__None(self): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - - loader = unittest.TestLoader() - loader.sortTestMethodsUsing = None - - test_names = ['test_2', 'test_1'] - self.assertEqual(set(loader.getTestCaseNames(Foo)), set(test_names)) - - ################################################################ - ### /Tests for TestLoader.sortTestMethodsUsing - - ### Tests for TestLoader.suiteClass - ################################################################ - - # "Callable object that constructs a test suite from a list of tests." - def test_suiteClass__loadTestsFromTestCase(self): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - - tests = [Foo('test_1'), Foo('test_2')] - - loader = unittest.TestLoader() - loader.suiteClass = list - self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) - - # It is implicit in the documentation for TestLoader.suiteClass that - # all TestLoader.loadTestsFrom* methods respect it. Let's make sure - def test_suiteClass__loadTestsFromModule(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests = [[Foo('test_1'), Foo('test_2')]] - - loader = unittest.TestLoader() - loader.suiteClass = list - self.assertEqual(loader.loadTestsFromModule(m), tests) - - # It is implicit in the documentation for TestLoader.suiteClass that - # all TestLoader.loadTestsFrom* methods respect it. Let's make sure - def test_suiteClass__loadTestsFromName(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests = [Foo('test_1'), Foo('test_2')] - - loader = unittest.TestLoader() - loader.suiteClass = list - self.assertEqual(loader.loadTestsFromName('Foo', m), tests) - - # It is implicit in the documentation for TestLoader.suiteClass that - # all TestLoader.loadTestsFrom* methods respect it. Let's make sure - def test_suiteClass__loadTestsFromNames(self): - m = types.ModuleType('m') - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def foo_bar(self): pass - m.Foo = Foo - - tests = [[Foo('test_1'), Foo('test_2')]] - - loader = unittest.TestLoader() - loader.suiteClass = list - self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests) - - # "The default value is the TestSuite class" - def test_suiteClass__default_value(self): - loader = unittest.TestLoader() - self.assertIs(loader.suiteClass, unittest.TestSuite) - - - def test_partial_functions(self): - def noop(arg): - pass - - class Foo(unittest.TestCase): - pass - - setattr(Foo, 'test_partial', functools.partial(noop, None)) - - loader = unittest.TestLoader() - - test_names = ['test_partial'] - self.assertEqual(loader.getTestCaseNames(Foo), test_names) - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_program.py b/Monika After Story/game/python-packages/unittest/test/test_program.py deleted file mode 100644 index eef82ff937..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_program.py +++ /dev/null @@ -1,440 +0,0 @@ -import io - -import os -import sys -import subprocess -from test import support -import unittest -import unittest.test - - -class Test_TestProgram(unittest.TestCase): - - def test_discovery_from_dotted_path(self): - loader = unittest.TestLoader() - - tests = [self] - expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__)) - - self.wasRun = False - def _find_tests(start_dir, pattern): - self.wasRun = True - self.assertEqual(start_dir, expectedPath) - return tests - loader._find_tests = _find_tests - suite = loader.discover('unittest.test') - self.assertTrue(self.wasRun) - self.assertEqual(suite._tests, tests) - - # Horrible white box test - def testNoExit(self): - result = object() - test = object() - - class FakeRunner(object): - def run(self, test): - self.test = test - return result - - runner = FakeRunner() - - oldParseArgs = unittest.TestProgram.parseArgs - def restoreParseArgs(): - unittest.TestProgram.parseArgs = oldParseArgs - unittest.TestProgram.parseArgs = lambda *args: None - self.addCleanup(restoreParseArgs) - - def removeTest(): - del unittest.TestProgram.test - unittest.TestProgram.test = test - self.addCleanup(removeTest) - - program = unittest.TestProgram(testRunner=runner, exit=False, verbosity=2) - - self.assertEqual(program.result, result) - self.assertEqual(runner.test, test) - self.assertEqual(program.verbosity, 2) - - class FooBar(unittest.TestCase): - def testPass(self): - assert True - def testFail(self): - assert False - - class FooBarLoader(unittest.TestLoader): - """Test loader that returns a suite containing FooBar.""" - def loadTestsFromModule(self, module): - return self.suiteClass( - [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) - - def loadTestsFromNames(self, names, module): - return self.suiteClass( - [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) - - def test_defaultTest_with_string(self): - class FakeRunner(object): - def run(self, test): - self.test = test - return True - - old_argv = sys.argv - sys.argv = ['faketest'] - runner = FakeRunner() - program = unittest.TestProgram(testRunner=runner, exit=False, - defaultTest='unittest.test', - testLoader=self.FooBarLoader()) - sys.argv = old_argv - self.assertEqual(('unittest.test',), program.testNames) - - def test_defaultTest_with_iterable(self): - class FakeRunner(object): - def run(self, test): - self.test = test - return True - - old_argv = sys.argv - sys.argv = ['faketest'] - runner = FakeRunner() - program = unittest.TestProgram( - testRunner=runner, exit=False, - defaultTest=['unittest.test', 'unittest.test2'], - testLoader=self.FooBarLoader()) - sys.argv = old_argv - self.assertEqual(['unittest.test', 'unittest.test2'], - program.testNames) - - def test_NonExit(self): - program = unittest.main(exit=False, - argv=["foobar"], - testRunner=unittest.TextTestRunner(stream=io.StringIO()), - testLoader=self.FooBarLoader()) - self.assertTrue(hasattr(program, 'result')) - - - def test_Exit(self): - self.assertRaises( - SystemExit, - unittest.main, - argv=["foobar"], - testRunner=unittest.TextTestRunner(stream=io.StringIO()), - exit=True, - testLoader=self.FooBarLoader()) - - - def test_ExitAsDefault(self): - self.assertRaises( - SystemExit, - unittest.main, - argv=["foobar"], - testRunner=unittest.TextTestRunner(stream=io.StringIO()), - testLoader=self.FooBarLoader()) - - -class InitialisableProgram(unittest.TestProgram): - exit = False - result = None - verbosity = 1 - defaultTest = None - tb_locals = False - testRunner = None - testLoader = unittest.defaultTestLoader - module = '__main__' - progName = 'test' - test = 'test' - def __init__(self, *args): - pass - -RESULT = object() - -class FakeRunner(object): - initArgs = None - test = None - raiseError = 0 - - def __init__(self, **kwargs): - FakeRunner.initArgs = kwargs - if FakeRunner.raiseError: - FakeRunner.raiseError -= 1 - raise TypeError - - def run(self, test): - FakeRunner.test = test - return RESULT - - -class TestCommandLineArgs(unittest.TestCase): - - def setUp(self): - self.program = InitialisableProgram() - self.program.createTests = lambda: None - FakeRunner.initArgs = None - FakeRunner.test = None - FakeRunner.raiseError = 0 - - def testVerbosity(self): - program = self.program - - for opt in '-q', '--quiet': - program.verbosity = 1 - program.parseArgs([None, opt]) - self.assertEqual(program.verbosity, 0) - - for opt in '-v', '--verbose': - program.verbosity = 1 - program.parseArgs([None, opt]) - self.assertEqual(program.verbosity, 2) - - def testBufferCatchFailfast(self): - program = self.program - for arg, attr in (('buffer', 'buffer'), ('failfast', 'failfast'), - ('catch', 'catchbreak')): - - setattr(program, attr, None) - program.parseArgs([None]) - self.assertIs(getattr(program, attr), False) - - false = [] - setattr(program, attr, false) - program.parseArgs([None]) - self.assertIs(getattr(program, attr), false) - - true = [42] - setattr(program, attr, true) - program.parseArgs([None]) - self.assertIs(getattr(program, attr), true) - - short_opt = '-%s' % arg[0] - long_opt = '--%s' % arg - for opt in short_opt, long_opt: - setattr(program, attr, None) - program.parseArgs([None, opt]) - self.assertIs(getattr(program, attr), True) - - setattr(program, attr, False) - with support.captured_stderr() as stderr, \ - self.assertRaises(SystemExit) as cm: - program.parseArgs([None, opt]) - self.assertEqual(cm.exception.args, (2,)) - - setattr(program, attr, True) - with support.captured_stderr() as stderr, \ - self.assertRaises(SystemExit) as cm: - program.parseArgs([None, opt]) - self.assertEqual(cm.exception.args, (2,)) - - def testWarning(self): - """Test the warnings argument""" - # see #10535 - class FakeTP(unittest.TestProgram): - def parseArgs(self, *args, **kw): pass - def runTests(self, *args, **kw): pass - warnoptions = sys.warnoptions[:] - try: - sys.warnoptions[:] = [] - # no warn options, no arg -> default - self.assertEqual(FakeTP().warnings, 'default') - # no warn options, w/ arg -> arg value - self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore') - sys.warnoptions[:] = ['somevalue'] - # warn options, no arg -> None - # warn options, w/ arg -> arg value - self.assertEqual(FakeTP().warnings, None) - self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore') - finally: - sys.warnoptions[:] = warnoptions - - def testRunTestsRunnerClass(self): - program = self.program - - program.testRunner = FakeRunner - program.verbosity = 'verbosity' - program.failfast = 'failfast' - program.buffer = 'buffer' - program.warnings = 'warnings' - - program.runTests() - - self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity', - 'failfast': 'failfast', - 'buffer': 'buffer', - 'tb_locals': False, - 'warnings': 'warnings'}) - self.assertEqual(FakeRunner.test, 'test') - self.assertIs(program.result, RESULT) - - def testRunTestsRunnerInstance(self): - program = self.program - - program.testRunner = FakeRunner() - FakeRunner.initArgs = None - - program.runTests() - - # A new FakeRunner should not have been instantiated - self.assertIsNone(FakeRunner.initArgs) - - self.assertEqual(FakeRunner.test, 'test') - self.assertIs(program.result, RESULT) - - def test_locals(self): - program = self.program - - program.testRunner = FakeRunner - program.parseArgs([None, '--locals']) - self.assertEqual(True, program.tb_locals) - program.runTests() - self.assertEqual(FakeRunner.initArgs, {'buffer': False, - 'failfast': False, - 'tb_locals': True, - 'verbosity': 1, - 'warnings': None}) - - def testRunTestsOldRunnerClass(self): - program = self.program - - # Two TypeErrors are needed to fall all the way back to old-style - # runners - one to fail tb_locals, one to fail buffer etc. - FakeRunner.raiseError = 2 - program.testRunner = FakeRunner - program.verbosity = 'verbosity' - program.failfast = 'failfast' - program.buffer = 'buffer' - program.test = 'test' - - program.runTests() - - # If initialising raises a type error it should be retried - # without the new keyword arguments - self.assertEqual(FakeRunner.initArgs, {}) - self.assertEqual(FakeRunner.test, 'test') - self.assertIs(program.result, RESULT) - - def testCatchBreakInstallsHandler(self): - module = sys.modules['unittest.main'] - original = module.installHandler - def restore(): - module.installHandler = original - self.addCleanup(restore) - - self.installed = False - def fakeInstallHandler(): - self.installed = True - module.installHandler = fakeInstallHandler - - program = self.program - program.catchbreak = True - - program.testRunner = FakeRunner - - program.runTests() - self.assertTrue(self.installed) - - def _patch_isfile(self, names, exists=True): - def isfile(path): - return path in names - original = os.path.isfile - os.path.isfile = isfile - def restore(): - os.path.isfile = original - self.addCleanup(restore) - - - def testParseArgsFileNames(self): - # running tests with filenames instead of module names - program = self.program - argv = ['progname', 'foo.py', 'bar.Py', 'baz.PY', 'wing.txt'] - self._patch_isfile(argv) - - program.createTests = lambda: None - program.parseArgs(argv) - - # note that 'wing.txt' is not a Python file so the name should - # *not* be converted to a module name - expected = ['foo', 'bar', 'baz', 'wing.txt'] - self.assertEqual(program.testNames, expected) - - - def testParseArgsFilePaths(self): - program = self.program - argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] - self._patch_isfile(argv) - - program.createTests = lambda: None - program.parseArgs(argv) - - expected = ['foo.bar.baz', 'green.red'] - self.assertEqual(program.testNames, expected) - - - def testParseArgsNonExistentFiles(self): - program = self.program - argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] - self._patch_isfile([]) - - program.createTests = lambda: None - program.parseArgs(argv) - - self.assertEqual(program.testNames, argv[1:]) - - def testParseArgsAbsolutePathsThatCanBeConverted(self): - cur_dir = os.getcwd() - program = self.program - def _join(name): - return os.path.join(cur_dir, name) - argv = ['progname', _join('foo/bar/baz.py'), _join('green\\red.py')] - self._patch_isfile(argv) - - program.createTests = lambda: None - program.parseArgs(argv) - - expected = ['foo.bar.baz', 'green.red'] - self.assertEqual(program.testNames, expected) - - def testParseArgsAbsolutePathsThatCannotBeConverted(self): - program = self.program - # even on Windows '/...' is considered absolute by os.path.abspath - argv = ['progname', '/foo/bar/baz.py', '/green/red.py'] - self._patch_isfile(argv) - - program.createTests = lambda: None - program.parseArgs(argv) - - self.assertEqual(program.testNames, argv[1:]) - - # it may be better to use platform specific functions to normalise paths - # rather than accepting '.PY' and '\' as file separator on Linux / Mac - # it would also be better to check that a filename is a valid module - # identifier (we have a regex for this in loader.py) - # for invalid filenames should we raise a useful error rather than - # leaving the current error message (import of filename fails) in place? - - def testParseArgsSelectedTestNames(self): - program = self.program - argv = ['progname', '-k', 'foo', '-k', 'bar', '-k', '*pat*'] - - program.createTests = lambda: None - program.parseArgs(argv) - - self.assertEqual(program.testNamePatterns, ['*foo*', '*bar*', '*pat*']) - - def testSelectedTestNamesFunctionalTest(self): - def run_unittest(args): - p = subprocess.Popen([sys.executable, '-m', 'unittest'] + args, - stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, cwd=os.path.dirname(__file__)) - with p: - _, stderr = p.communicate() - return stderr.decode() - - t = '_test_warnings' - self.assertIn('Ran 7 tests', run_unittest([t])) - self.assertIn('Ran 7 tests', run_unittest(['-k', 'TestWarnings', t])) - self.assertIn('Ran 7 tests', run_unittest(['discover', '-p', '*_test*', '-k', 'TestWarnings'])) - self.assertIn('Ran 2 tests', run_unittest(['-k', 'f', t])) - self.assertIn('Ran 7 tests', run_unittest(['-k', 't', t])) - self.assertIn('Ran 3 tests', run_unittest(['-k', '*t', t])) - self.assertIn('Ran 7 tests', run_unittest(['-k', '*test_warnings.*Warning*', t])) - self.assertIn('Ran 1 test', run_unittest(['-k', '*test_warnings.*warning*', t])) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_result.py b/Monika After Story/game/python-packages/unittest/test/test_result.py deleted file mode 100644 index 0ffb87b402..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_result.py +++ /dev/null @@ -1,704 +0,0 @@ -import io -import sys -import textwrap - -from test import support - -import traceback -import unittest - - -class MockTraceback(object): - class TracebackException: - def __init__(self, *args, **kwargs): - self.capture_locals = kwargs.get('capture_locals', False) - def format(self): - result = ['A traceback'] - if self.capture_locals: - result.append('locals') - return result - -def restore_traceback(): - unittest.result.traceback = traceback - - -class Test_TestResult(unittest.TestCase): - # Note: there are not separate tests for TestResult.wasSuccessful(), - # TestResult.errors, TestResult.failures, TestResult.testsRun or - # TestResult.shouldStop because these only have meaning in terms of - # other TestResult methods. - # - # Accordingly, tests for the aforenamed attributes are incorporated - # in with the tests for the defining methods. - ################################################################ - - def test_init(self): - result = unittest.TestResult() - - self.assertTrue(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 0) - self.assertEqual(result.shouldStop, False) - self.assertIsNone(result._stdout_buffer) - self.assertIsNone(result._stderr_buffer) - - # "This method can be called to signal that the set of tests being - # run should be aborted by setting the TestResult's shouldStop - # attribute to True." - def test_stop(self): - result = unittest.TestResult() - - result.stop() - - self.assertEqual(result.shouldStop, True) - - # "Called when the test case test is about to be run. The default - # implementation simply increments the instance's testsRun counter." - def test_startTest(self): - class Foo(unittest.TestCase): - def test_1(self): - pass - - test = Foo('test_1') - - result = unittest.TestResult() - - result.startTest(test) - - self.assertTrue(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - result.stopTest(test) - - # "Called after the test case test has been executed, regardless of - # the outcome. The default implementation does nothing." - def test_stopTest(self): - class Foo(unittest.TestCase): - def test_1(self): - pass - - test = Foo('test_1') - - result = unittest.TestResult() - - result.startTest(test) - - self.assertTrue(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - result.stopTest(test) - - # Same tests as above; make sure nothing has changed - self.assertTrue(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - # "Called before and after tests are run. The default implementation does nothing." - def test_startTestRun_stopTestRun(self): - result = unittest.TestResult() - result.startTestRun() - result.stopTestRun() - - # "addSuccess(test)" - # ... - # "Called when the test case test succeeds" - # ... - # "wasSuccessful() - Returns True if all tests run so far have passed, - # otherwise returns False" - # ... - # "testsRun - The total number of tests run so far." - # ... - # "errors - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test which raised an - # unexpected exception. Contains formatted - # tracebacks instead of sys.exc_info() results." - # ... - # "failures - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test where a failure was - # explicitly signalled using the TestCase.fail*() or TestCase.assert*() - # methods. Contains formatted tracebacks instead - # of sys.exc_info() results." - def test_addSuccess(self): - class Foo(unittest.TestCase): - def test_1(self): - pass - - test = Foo('test_1') - - result = unittest.TestResult() - - result.startTest(test) - result.addSuccess(test) - result.stopTest(test) - - self.assertTrue(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - # "addFailure(test, err)" - # ... - # "Called when the test case test signals a failure. err is a tuple of - # the form returned by sys.exc_info(): (type, value, traceback)" - # ... - # "wasSuccessful() - Returns True if all tests run so far have passed, - # otherwise returns False" - # ... - # "testsRun - The total number of tests run so far." - # ... - # "errors - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test which raised an - # unexpected exception. Contains formatted - # tracebacks instead of sys.exc_info() results." - # ... - # "failures - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test where a failure was - # explicitly signalled using the TestCase.fail*() or TestCase.assert*() - # methods. Contains formatted tracebacks instead - # of sys.exc_info() results." - def test_addFailure(self): - class Foo(unittest.TestCase): - def test_1(self): - pass - - test = Foo('test_1') - try: - test.fail("foo") - except: - exc_info_tuple = sys.exc_info() - - result = unittest.TestResult() - - result.startTest(test) - result.addFailure(test, exc_info_tuple) - result.stopTest(test) - - self.assertFalse(result.wasSuccessful()) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.failures), 1) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - test_case, formatted_exc = result.failures[0] - self.assertIs(test_case, test) - self.assertIsInstance(formatted_exc, str) - - # "addError(test, err)" - # ... - # "Called when the test case test raises an unexpected exception err - # is a tuple of the form returned by sys.exc_info(): - # (type, value, traceback)" - # ... - # "wasSuccessful() - Returns True if all tests run so far have passed, - # otherwise returns False" - # ... - # "testsRun - The total number of tests run so far." - # ... - # "errors - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test which raised an - # unexpected exception. Contains formatted - # tracebacks instead of sys.exc_info() results." - # ... - # "failures - A list containing 2-tuples of TestCase instances and - # formatted tracebacks. Each tuple represents a test where a failure was - # explicitly signalled using the TestCase.fail*() or TestCase.assert*() - # methods. Contains formatted tracebacks instead - # of sys.exc_info() results." - def test_addError(self): - class Foo(unittest.TestCase): - def test_1(self): - pass - - test = Foo('test_1') - try: - raise TypeError() - except: - exc_info_tuple = sys.exc_info() - - result = unittest.TestResult() - - result.startTest(test) - result.addError(test, exc_info_tuple) - result.stopTest(test) - - self.assertFalse(result.wasSuccessful()) - self.assertEqual(len(result.errors), 1) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - test_case, formatted_exc = result.errors[0] - self.assertIs(test_case, test) - self.assertIsInstance(formatted_exc, str) - - def test_addError_locals(self): - class Foo(unittest.TestCase): - def test_1(self): - 1/0 - - test = Foo('test_1') - result = unittest.TestResult() - result.tb_locals = True - - unittest.result.traceback = MockTraceback - self.addCleanup(restore_traceback) - result.startTestRun() - test.run(result) - result.stopTestRun() - - self.assertEqual(len(result.errors), 1) - test_case, formatted_exc = result.errors[0] - self.assertEqual('A tracebacklocals', formatted_exc) - - def test_addSubTest(self): - class Foo(unittest.TestCase): - def test_1(self): - nonlocal subtest - with self.subTest(foo=1): - subtest = self._subtest - try: - 1/0 - except ZeroDivisionError: - exc_info_tuple = sys.exc_info() - # Register an error by hand (to check the API) - result.addSubTest(test, subtest, exc_info_tuple) - # Now trigger a failure - self.fail("some recognizable failure") - - subtest = None - test = Foo('test_1') - result = unittest.TestResult() - - test.run(result) - - self.assertFalse(result.wasSuccessful()) - self.assertEqual(len(result.errors), 1) - self.assertEqual(len(result.failures), 1) - self.assertEqual(result.testsRun, 1) - self.assertEqual(result.shouldStop, False) - - test_case, formatted_exc = result.errors[0] - self.assertIs(test_case, subtest) - self.assertIn("ZeroDivisionError", formatted_exc) - test_case, formatted_exc = result.failures[0] - self.assertIs(test_case, subtest) - self.assertIn("some recognizable failure", formatted_exc) - - def testGetDescriptionWithoutDocstring(self): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self), - 'testGetDescriptionWithoutDocstring (' + __name__ + - '.Test_TestResult)') - - def testGetSubTestDescriptionWithoutDocstring(self): - with self.subTest(foo=1, bar=2): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self._subtest), - 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + - '.Test_TestResult) (foo=1, bar=2)') - with self.subTest('some message'): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self._subtest), - 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + - '.Test_TestResult) [some message]') - - def testGetSubTestDescriptionWithoutDocstringAndParams(self): - with self.subTest(): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self._subtest), - 'testGetSubTestDescriptionWithoutDocstringAndParams ' - '(' + __name__ + '.Test_TestResult) ()') - - def testGetSubTestDescriptionForFalsyValues(self): - expected = 'testGetSubTestDescriptionForFalsyValues (%s.Test_TestResult) [%s]' - result = unittest.TextTestResult(None, True, 1) - for arg in [0, None, []]: - with self.subTest(arg): - self.assertEqual( - result.getDescription(self._subtest), - expected % (__name__, arg) - ) - - def testGetNestedSubTestDescriptionWithoutDocstring(self): - with self.subTest(foo=1): - with self.subTest(baz=2, bar=3): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self._subtest), - 'testGetNestedSubTestDescriptionWithoutDocstring ' - '(' + __name__ + '.Test_TestResult) (baz=2, bar=3, foo=1)') - - def testGetDuplicatedNestedSubTestDescriptionWithoutDocstring(self): - with self.subTest(foo=1, bar=2): - with self.subTest(baz=3, bar=4): - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self._subtest), - 'testGetDuplicatedNestedSubTestDescriptionWithoutDocstring ' - '(' + __name__ + '.Test_TestResult) (baz=3, bar=4, foo=1)') - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testGetDescriptionWithOneLineDocstring(self): - """Tests getDescription() for a method with a docstring.""" - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self), - ('testGetDescriptionWithOneLineDocstring ' - '(' + __name__ + '.Test_TestResult)\n' - 'Tests getDescription() for a method with a docstring.')) - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testGetSubTestDescriptionWithOneLineDocstring(self): - """Tests getDescription() for a method with a docstring.""" - result = unittest.TextTestResult(None, True, 1) - with self.subTest(foo=1, bar=2): - self.assertEqual( - result.getDescription(self._subtest), - ('testGetSubTestDescriptionWithOneLineDocstring ' - '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' - 'Tests getDescription() for a method with a docstring.')) - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testGetDescriptionWithMultiLineDocstring(self): - """Tests getDescription() for a method with a longer docstring. - The second line of the docstring. - """ - result = unittest.TextTestResult(None, True, 1) - self.assertEqual( - result.getDescription(self), - ('testGetDescriptionWithMultiLineDocstring ' - '(' + __name__ + '.Test_TestResult)\n' - 'Tests getDescription() for a method with a longer ' - 'docstring.')) - - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testGetSubTestDescriptionWithMultiLineDocstring(self): - """Tests getDescription() for a method with a longer docstring. - The second line of the docstring. - """ - result = unittest.TextTestResult(None, True, 1) - with self.subTest(foo=1, bar=2): - self.assertEqual( - result.getDescription(self._subtest), - ('testGetSubTestDescriptionWithMultiLineDocstring ' - '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' - 'Tests getDescription() for a method with a longer ' - 'docstring.')) - - def testStackFrameTrimming(self): - class Frame(object): - class tb_frame(object): - f_globals = {} - result = unittest.TestResult() - self.assertFalse(result._is_relevant_tb_level(Frame)) - - Frame.tb_frame.f_globals['__unittest'] = True - self.assertTrue(result._is_relevant_tb_level(Frame)) - - def testFailFast(self): - result = unittest.TestResult() - result._exc_info_to_string = lambda *_: '' - result.failfast = True - result.addError(None, None) - self.assertTrue(result.shouldStop) - - result = unittest.TestResult() - result._exc_info_to_string = lambda *_: '' - result.failfast = True - result.addFailure(None, None) - self.assertTrue(result.shouldStop) - - result = unittest.TestResult() - result._exc_info_to_string = lambda *_: '' - result.failfast = True - result.addUnexpectedSuccess(None) - self.assertTrue(result.shouldStop) - - def testFailFastSetByRunner(self): - runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True) - def test(result): - self.assertTrue(result.failfast) - result = runner.run(test) - - -classDict = dict(unittest.TestResult.__dict__) -for m in ('addSkip', 'addExpectedFailure', 'addUnexpectedSuccess', - '__init__'): - del classDict[m] - -def __init__(self, stream=None, descriptions=None, verbosity=None): - self.failures = [] - self.errors = [] - self.testsRun = 0 - self.shouldStop = False - self.buffer = False - self.tb_locals = False - -classDict['__init__'] = __init__ -OldResult = type('OldResult', (object,), classDict) - -class Test_OldTestResult(unittest.TestCase): - - def assertOldResultWarning(self, test, failures): - with support.check_warnings(("TestResult has no add.+ method,", - RuntimeWarning)): - result = OldResult() - test.run(result) - self.assertEqual(len(result.failures), failures) - - def testOldTestResult(self): - class Test(unittest.TestCase): - def testSkip(self): - self.skipTest('foobar') - @unittest.expectedFailure - def testExpectedFail(self): - raise TypeError - @unittest.expectedFailure - def testUnexpectedSuccess(self): - pass - - for test_name, should_pass in (('testSkip', True), - ('testExpectedFail', True), - ('testUnexpectedSuccess', False)): - test = Test(test_name) - self.assertOldResultWarning(test, int(not should_pass)) - - def testOldTestTesultSetup(self): - class Test(unittest.TestCase): - def setUp(self): - self.skipTest('no reason') - def testFoo(self): - pass - self.assertOldResultWarning(Test('testFoo'), 0) - - def testOldTestResultClass(self): - @unittest.skip('no reason') - class Test(unittest.TestCase): - def testFoo(self): - pass - self.assertOldResultWarning(Test('testFoo'), 0) - - def testOldResultWithRunner(self): - class Test(unittest.TestCase): - def testFoo(self): - pass - runner = unittest.TextTestRunner(resultclass=OldResult, - stream=io.StringIO()) - # This will raise an exception if TextTestRunner can't handle old - # test result objects - runner.run(Test('testFoo')) - - -class TestOutputBuffering(unittest.TestCase): - - def setUp(self): - self._real_out = sys.stdout - self._real_err = sys.stderr - - def tearDown(self): - sys.stdout = self._real_out - sys.stderr = self._real_err - - def testBufferOutputOff(self): - real_out = self._real_out - real_err = self._real_err - - result = unittest.TestResult() - self.assertFalse(result.buffer) - - self.assertIs(real_out, sys.stdout) - self.assertIs(real_err, sys.stderr) - - result.startTest(self) - - self.assertIs(real_out, sys.stdout) - self.assertIs(real_err, sys.stderr) - - def testBufferOutputStartTestAddSuccess(self): - real_out = self._real_out - real_err = self._real_err - - result = unittest.TestResult() - self.assertFalse(result.buffer) - - result.buffer = True - - self.assertIs(real_out, sys.stdout) - self.assertIs(real_err, sys.stderr) - - result.startTest(self) - - self.assertIsNot(real_out, sys.stdout) - self.assertIsNot(real_err, sys.stderr) - self.assertIsInstance(sys.stdout, io.StringIO) - self.assertIsInstance(sys.stderr, io.StringIO) - self.assertIsNot(sys.stdout, sys.stderr) - - out_stream = sys.stdout - err_stream = sys.stderr - - result._original_stdout = io.StringIO() - result._original_stderr = io.StringIO() - - print('foo') - print('bar', file=sys.stderr) - - self.assertEqual(out_stream.getvalue(), 'foo\n') - self.assertEqual(err_stream.getvalue(), 'bar\n') - - self.assertEqual(result._original_stdout.getvalue(), '') - self.assertEqual(result._original_stderr.getvalue(), '') - - result.addSuccess(self) - result.stopTest(self) - - self.assertIs(sys.stdout, result._original_stdout) - self.assertIs(sys.stderr, result._original_stderr) - - self.assertEqual(result._original_stdout.getvalue(), '') - self.assertEqual(result._original_stderr.getvalue(), '') - - self.assertEqual(out_stream.getvalue(), '') - self.assertEqual(err_stream.getvalue(), '') - - - def getStartedResult(self): - result = unittest.TestResult() - result.buffer = True - result.startTest(self) - return result - - def testBufferOutputAddErrorOrFailure(self): - unittest.result.traceback = MockTraceback - self.addCleanup(restore_traceback) - - for message_attr, add_attr, include_error in [ - ('errors', 'addError', True), - ('failures', 'addFailure', False), - ('errors', 'addError', True), - ('failures', 'addFailure', False) - ]: - result = self.getStartedResult() - buffered_out = sys.stdout - buffered_err = sys.stderr - result._original_stdout = io.StringIO() - result._original_stderr = io.StringIO() - - print('foo', file=sys.stdout) - if include_error: - print('bar', file=sys.stderr) - - - addFunction = getattr(result, add_attr) - addFunction(self, (None, None, None)) - result.stopTest(self) - - result_list = getattr(result, message_attr) - self.assertEqual(len(result_list), 1) - - test, message = result_list[0] - expectedOutMessage = textwrap.dedent(""" - Stdout: - foo - """) - expectedErrMessage = '' - if include_error: - expectedErrMessage = textwrap.dedent(""" - Stderr: - bar - """) - - expectedFullMessage = 'A traceback%s%s' % (expectedOutMessage, expectedErrMessage) - - self.assertIs(test, self) - self.assertEqual(result._original_stdout.getvalue(), expectedOutMessage) - self.assertEqual(result._original_stderr.getvalue(), expectedErrMessage) - self.assertMultiLineEqual(message, expectedFullMessage) - - def testBufferSetupClass(self): - result = unittest.TestResult() - result.buffer = True - - class Foo(unittest.TestCase): - @classmethod - def setUpClass(cls): - 1/0 - def test_foo(self): - pass - suite = unittest.TestSuite([Foo('test_foo')]) - suite(result) - self.assertEqual(len(result.errors), 1) - - def testBufferTearDownClass(self): - result = unittest.TestResult() - result.buffer = True - - class Foo(unittest.TestCase): - @classmethod - def tearDownClass(cls): - 1/0 - def test_foo(self): - pass - suite = unittest.TestSuite([Foo('test_foo')]) - suite(result) - self.assertEqual(len(result.errors), 1) - - def testBufferSetUpModule(self): - result = unittest.TestResult() - result.buffer = True - - class Foo(unittest.TestCase): - def test_foo(self): - pass - class Module(object): - @staticmethod - def setUpModule(): - 1/0 - - Foo.__module__ = 'Module' - sys.modules['Module'] = Module - self.addCleanup(sys.modules.pop, 'Module') - suite = unittest.TestSuite([Foo('test_foo')]) - suite(result) - self.assertEqual(len(result.errors), 1) - - def testBufferTearDownModule(self): - result = unittest.TestResult() - result.buffer = True - - class Foo(unittest.TestCase): - def test_foo(self): - pass - class Module(object): - @staticmethod - def tearDownModule(): - 1/0 - - Foo.__module__ = 'Module' - sys.modules['Module'] = Module - self.addCleanup(sys.modules.pop, 'Module') - suite = unittest.TestSuite([Foo('test_foo')]) - suite(result) - self.assertEqual(len(result.errors), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_runner.py b/Monika After Story/game/python-packages/unittest/test/test_runner.py deleted file mode 100644 index dd9a1b6d9a..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_runner.py +++ /dev/null @@ -1,1013 +0,0 @@ -import io -import os -import sys -import pickle -import subprocess - -import unittest -from unittest.case import _Outcome - -from unittest.test.support import (LoggingResult, - ResultWithNoStartTestRunStopTestRun) - - -def resultFactory(*_): - return unittest.TestResult() - - -def getRunner(): - return unittest.TextTestRunner(resultclass=resultFactory, - stream=io.StringIO()) - - -def runTests(*cases): - suite = unittest.TestSuite() - for case in cases: - tests = unittest.defaultTestLoader.loadTestsFromTestCase(case) - suite.addTests(tests) - - runner = getRunner() - - # creating a nested suite exposes some potential bugs - realSuite = unittest.TestSuite() - realSuite.addTest(suite) - # adding empty suites to the end exposes potential bugs - suite.addTest(unittest.TestSuite()) - realSuite.addTest(unittest.TestSuite()) - return runner.run(realSuite) - - -def cleanup(ordering, blowUp=False): - if not blowUp: - ordering.append('cleanup_good') - else: - ordering.append('cleanup_exc') - raise Exception('CleanUpExc') - - -class TestCleanUp(unittest.TestCase): - def testCleanUp(self): - class TestableTest(unittest.TestCase): - def testNothing(self): - pass - - test = TestableTest('testNothing') - self.assertEqual(test._cleanups, []) - - cleanups = [] - - def cleanup1(*args, **kwargs): - cleanups.append((1, args, kwargs)) - - def cleanup2(*args, **kwargs): - cleanups.append((2, args, kwargs)) - - test.addCleanup(cleanup1, 1, 2, 3, four='hello', five='goodbye') - test.addCleanup(cleanup2) - - self.assertEqual(test._cleanups, - [(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')), - (cleanup2, (), {})]) - - self.assertTrue(test.doCleanups()) - self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))]) - - def testCleanUpWithErrors(self): - class TestableTest(unittest.TestCase): - def testNothing(self): - pass - - test = TestableTest('testNothing') - outcome = test._outcome = _Outcome() - - CleanUpExc = Exception('foo') - exc2 = Exception('bar') - def cleanup1(): - raise CleanUpExc - - def cleanup2(): - raise exc2 - - test.addCleanup(cleanup1) - test.addCleanup(cleanup2) - - self.assertFalse(test.doCleanups()) - self.assertFalse(outcome.success) - - ((_, (Type1, instance1, _)), - (_, (Type2, instance2, _))) = reversed(outcome.errors) - self.assertEqual((Type1, instance1), (Exception, CleanUpExc)) - self.assertEqual((Type2, instance2), (Exception, exc2)) - - def testCleanupInRun(self): - blowUp = False - ordering = [] - - class TestableTest(unittest.TestCase): - def setUp(self): - ordering.append('setUp') - if blowUp: - raise Exception('foo') - - def testNothing(self): - ordering.append('test') - - def tearDown(self): - ordering.append('tearDown') - - test = TestableTest('testNothing') - - def cleanup1(): - ordering.append('cleanup1') - def cleanup2(): - ordering.append('cleanup2') - test.addCleanup(cleanup1) - test.addCleanup(cleanup2) - - def success(some_test): - self.assertEqual(some_test, test) - ordering.append('success') - - result = unittest.TestResult() - result.addSuccess = success - - test.run(result) - self.assertEqual(ordering, ['setUp', 'test', 'tearDown', - 'cleanup2', 'cleanup1', 'success']) - - blowUp = True - ordering = [] - test = TestableTest('testNothing') - test.addCleanup(cleanup1) - test.run(result) - self.assertEqual(ordering, ['setUp', 'cleanup1']) - - def testTestCaseDebugExecutesCleanups(self): - ordering = [] - - class TestableTest(unittest.TestCase): - def setUp(self): - ordering.append('setUp') - self.addCleanup(cleanup1) - - def testNothing(self): - ordering.append('test') - - def tearDown(self): - ordering.append('tearDown') - - test = TestableTest('testNothing') - - def cleanup1(): - ordering.append('cleanup1') - test.addCleanup(cleanup2) - def cleanup2(): - ordering.append('cleanup2') - - test.debug() - self.assertEqual(ordering, ['setUp', 'test', 'tearDown', 'cleanup1', 'cleanup2']) - - -class TestClassCleanup(unittest.TestCase): - def test_addClassCleanUp(self): - class TestableTest(unittest.TestCase): - def testNothing(self): - pass - test = TestableTest('testNothing') - self.assertEqual(test._class_cleanups, []) - class_cleanups = [] - - def class_cleanup1(*args, **kwargs): - class_cleanups.append((3, args, kwargs)) - - def class_cleanup2(*args, **kwargs): - class_cleanups.append((4, args, kwargs)) - - TestableTest.addClassCleanup(class_cleanup1, 1, 2, 3, - four='hello', five='goodbye') - TestableTest.addClassCleanup(class_cleanup2) - - self.assertEqual(test._class_cleanups, - [(class_cleanup1, (1, 2, 3), - dict(four='hello', five='goodbye')), - (class_cleanup2, (), {})]) - - TestableTest.doClassCleanups() - self.assertEqual(class_cleanups, [(4, (), {}), (3, (1, 2, 3), - dict(four='hello', five='goodbye'))]) - - def test_run_class_cleanUp(self): - ordering = [] - blowUp = True - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering) - if blowUp: - raise Exception() - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - runTests(TestableTest) - self.assertEqual(ordering, ['setUpClass', 'cleanup_good']) - - ordering = [] - blowUp = False - runTests(TestableTest) - self.assertEqual(ordering, - ['setUpClass', 'test', 'tearDownClass', 'cleanup_good']) - - def test_debug_executes_classCleanUp(self): - ordering = [] - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering) - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest) - suite.debug() - self.assertEqual(ordering, - ['setUpClass', 'test', 'tearDownClass', 'cleanup_good']) - - def test_doClassCleanups_with_errors_addClassCleanUp(self): - class TestableTest(unittest.TestCase): - def testNothing(self): - pass - - def cleanup1(): - raise Exception('cleanup1') - - def cleanup2(): - raise Exception('cleanup2') - - TestableTest.addClassCleanup(cleanup1) - TestableTest.addClassCleanup(cleanup2) - with self.assertRaises(Exception) as e: - TestableTest.doClassCleanups() - self.assertEqual(e, 'cleanup1') - - def test_with_errors_addCleanUp(self): - ordering = [] - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering) - def setUp(self): - ordering.append('setUp') - self.addCleanup(cleanup, ordering, blowUp=True) - def testNothing(self): - pass - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpClass', 'setUp', 'cleanup_exc', - 'tearDownClass', 'cleanup_good']) - - def test_run_with_errors_addClassCleanUp(self): - ordering = [] - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering, blowUp=True) - def setUp(self): - ordering.append('setUp') - self.addCleanup(cleanup, ordering) - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpClass', 'setUp', 'test', 'cleanup_good', - 'tearDownClass', 'cleanup_exc']) - - def test_with_errors_in_addClassCleanup_and_setUps(self): - ordering = [] - class_blow_up = False - method_blow_up = False - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering, blowUp=True) - if class_blow_up: - raise Exception('ClassExc') - def setUp(self): - ordering.append('setUp') - if method_blow_up: - raise Exception('MethodExc') - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpClass', 'setUp', 'test', - 'tearDownClass', 'cleanup_exc']) - ordering = [] - class_blow_up = True - method_blow_up = False - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: ClassExc') - self.assertEqual(result.errors[1][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpClass', 'cleanup_exc']) - - ordering = [] - class_blow_up = False - method_blow_up = True - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: MethodExc') - self.assertEqual(result.errors[1][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpClass', 'setUp', 'tearDownClass', - 'cleanup_exc']) - - -class TestModuleCleanUp(unittest.TestCase): - def test_add_and_do_ModuleCleanup(self): - module_cleanups = [] - - def module_cleanup1(*args, **kwargs): - module_cleanups.append((3, args, kwargs)) - - def module_cleanup2(*args, **kwargs): - module_cleanups.append((4, args, kwargs)) - - class Module(object): - unittest.addModuleCleanup(module_cleanup1, 1, 2, 3, - four='hello', five='goodbye') - unittest.addModuleCleanup(module_cleanup2) - - self.assertEqual(unittest.case._module_cleanups, - [(module_cleanup1, (1, 2, 3), - dict(four='hello', five='goodbye')), - (module_cleanup2, (), {})]) - - unittest.case.doModuleCleanups() - self.assertEqual(module_cleanups, [(4, (), {}), (3, (1, 2, 3), - dict(four='hello', five='goodbye'))]) - self.assertEqual(unittest.case._module_cleanups, []) - - def test_doModuleCleanup_with_errors_in_addModuleCleanup(self): - module_cleanups = [] - - def module_cleanup_good(*args, **kwargs): - module_cleanups.append((3, args, kwargs)) - - def module_cleanup_bad(*args, **kwargs): - raise Exception('CleanUpExc') - - class Module(object): - unittest.addModuleCleanup(module_cleanup_good, 1, 2, 3, - four='hello', five='goodbye') - unittest.addModuleCleanup(module_cleanup_bad) - self.assertEqual(unittest.case._module_cleanups, - [(module_cleanup_good, (1, 2, 3), - dict(four='hello', five='goodbye')), - (module_cleanup_bad, (), {})]) - with self.assertRaises(Exception) as e: - unittest.case.doModuleCleanups() - self.assertEqual(str(e.exception), 'CleanUpExc') - self.assertEqual(unittest.case._module_cleanups, []) - - def test_addModuleCleanup_arg_errors(self): - cleanups = [] - def cleanup(*args, **kwargs): - cleanups.append((args, kwargs)) - - class Module(object): - unittest.addModuleCleanup(cleanup, 1, 2, function='hello') - with self.assertRaises(TypeError): - unittest.addModuleCleanup(function=cleanup, arg='hello') - with self.assertRaises(TypeError): - unittest.addModuleCleanup() - unittest.case.doModuleCleanups() - self.assertEqual(cleanups, - [((1, 2), {'function': 'hello'})]) - - def test_run_module_cleanUp(self): - blowUp = True - ordering = [] - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering) - if blowUp: - raise Exception('setUpModule Exc') - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - TestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - result = runTests(TestableTest) - self.assertEqual(ordering, ['setUpModule', 'cleanup_good']) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: setUpModule Exc') - - ordering = [] - blowUp = False - runTests(TestableTest) - self.assertEqual(ordering, - ['setUpModule', 'setUpClass', 'test', 'tearDownClass', - 'tearDownModule', 'cleanup_good']) - self.assertEqual(unittest.case._module_cleanups, []) - - def test_run_multiple_module_cleanUp(self): - blowUp = True - blowUp2 = False - ordering = [] - class Module1(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering) - if blowUp: - raise Exception() - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class Module2(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule2') - unittest.addModuleCleanup(cleanup, ordering) - if blowUp2: - raise Exception() - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule2') - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - class TestableTest2(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass2') - def testNothing(self): - ordering.append('test2') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass2') - - TestableTest.__module__ = 'Module1' - sys.modules['Module1'] = Module1 - TestableTest2.__module__ = 'Module2' - sys.modules['Module2'] = Module2 - runTests(TestableTest, TestableTest2) - self.assertEqual(ordering, ['setUpModule', 'cleanup_good', - 'setUpModule2', 'setUpClass2', 'test2', - 'tearDownClass2', 'tearDownModule2', - 'cleanup_good']) - ordering = [] - blowUp = False - blowUp2 = True - runTests(TestableTest, TestableTest2) - self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'test', - 'tearDownClass', 'tearDownModule', - 'cleanup_good', 'setUpModule2', - 'cleanup_good']) - - ordering = [] - blowUp = False - blowUp2 = False - runTests(TestableTest, TestableTest2) - self.assertEqual(ordering, - ['setUpModule', 'setUpClass', 'test', 'tearDownClass', - 'tearDownModule', 'cleanup_good', 'setUpModule2', - 'setUpClass2', 'test2', 'tearDownClass2', - 'tearDownModule2', 'cleanup_good']) - self.assertEqual(unittest.case._module_cleanups, []) - - def test_debug_module_executes_cleanUp(self): - ordering = [] - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering) - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - TestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest) - suite.debug() - self.assertEqual(ordering, - ['setUpModule', 'setUpClass', 'test', 'tearDownClass', - 'tearDownModule', 'cleanup_good']) - self.assertEqual(unittest.case._module_cleanups, []) - - def test_addClassCleanup_arg_errors(self): - cleanups = [] - def cleanup(*args, **kwargs): - cleanups.append((args, kwargs)) - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.addClassCleanup(cleanup, 1, 2, function=3, cls=4) - with self.assertRaises(TypeError): - cls.addClassCleanup(function=cleanup, arg='hello') - def testNothing(self): - pass - - with self.assertRaises(TypeError): - TestableTest.addClassCleanup() - with self.assertRaises(TypeError): - unittest.TestCase.addCleanup(cls=TestableTest(), function=cleanup) - runTests(TestableTest) - self.assertEqual(cleanups, - [((1, 2), {'function': 3, 'cls': 4})]) - - def test_addCleanup_arg_errors(self): - cleanups = [] - def cleanup(*args, **kwargs): - cleanups.append((args, kwargs)) - - class TestableTest(unittest.TestCase): - def setUp(self2): - self2.addCleanup(cleanup, 1, 2, function=3, self=4) - with self.assertRaises(TypeError): - self2.addCleanup(function=cleanup, arg='hello') - def testNothing(self): - pass - - with self.assertRaises(TypeError): - TestableTest().addCleanup() - with self.assertRaises(TypeError): - unittest.TestCase.addCleanup(self=TestableTest(), function=cleanup) - runTests(TestableTest) - self.assertEqual(cleanups, - [((1, 2), {'function': 3, 'self': 4})]) - - def test_with_errors_in_addClassCleanup(self): - ordering = [] - - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering) - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - cls.addClassCleanup(cleanup, ordering, blowUp=True) - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - TestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpModule', 'setUpClass', 'test', 'tearDownClass', - 'cleanup_exc', 'tearDownModule', 'cleanup_good']) - - def test_with_errors_in_addCleanup(self): - ordering = [] - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering) - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - def setUp(self): - ordering.append('setUp') - self.addCleanup(cleanup, ordering, blowUp=True) - def testNothing(self): - ordering.append('test') - def tearDown(self): - ordering.append('tearDown') - - TestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpModule', 'setUp', 'test', 'tearDown', - 'cleanup_exc', 'tearDownModule', 'cleanup_good']) - - def test_with_errors_in_addModuleCleanup_and_setUps(self): - ordering = [] - module_blow_up = False - class_blow_up = False - method_blow_up = False - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup, ordering, blowUp=True) - if module_blow_up: - raise Exception('ModuleExc') - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - if class_blow_up: - raise Exception('ClassExc') - def setUp(self): - ordering.append('setUp') - if method_blow_up: - raise Exception('MethodExc') - def testNothing(self): - ordering.append('test') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - - TestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, - ['setUpModule', 'setUpClass', 'setUp', 'test', - 'tearDownClass', 'tearDownModule', - 'cleanup_exc']) - - ordering = [] - module_blow_up = True - class_blow_up = False - method_blow_up = False - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(result.errors[1][1].splitlines()[-1], - 'Exception: ModuleExc') - self.assertEqual(ordering, ['setUpModule', 'cleanup_exc']) - - ordering = [] - module_blow_up = False - class_blow_up = True - method_blow_up = False - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: ClassExc') - self.assertEqual(result.errors[1][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, ['setUpModule', 'setUpClass', - 'tearDownModule', 'cleanup_exc']) - - ordering = [] - module_blow_up = False - class_blow_up = False - method_blow_up = True - result = runTests(TestableTest) - self.assertEqual(result.errors[0][1].splitlines()[-1], - 'Exception: MethodExc') - self.assertEqual(result.errors[1][1].splitlines()[-1], - 'Exception: CleanUpExc') - self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'setUp', - 'tearDownClass', 'tearDownModule', - 'cleanup_exc']) - - def test_module_cleanUp_with_multiple_classes(self): - ordering =[] - def cleanup1(): - ordering.append('cleanup1') - - def cleanup2(): - ordering.append('cleanup2') - - def cleanup3(): - ordering.append('cleanup3') - - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - unittest.addModuleCleanup(cleanup1) - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class TestableTest(unittest.TestCase): - def setUp(self): - ordering.append('setUp') - self.addCleanup(cleanup2) - def testNothing(self): - ordering.append('test') - def tearDown(self): - ordering.append('tearDown') - - class OtherTestableTest(unittest.TestCase): - def setUp(self): - ordering.append('setUp2') - self.addCleanup(cleanup3) - def testNothing(self): - ordering.append('test2') - def tearDown(self): - ordering.append('tearDown2') - - TestableTest.__module__ = 'Module' - OtherTestableTest.__module__ = 'Module' - sys.modules['Module'] = Module - runTests(TestableTest, OtherTestableTest) - self.assertEqual(ordering, - ['setUpModule', 'setUp', 'test', 'tearDown', - 'cleanup2', 'setUp2', 'test2', 'tearDown2', - 'cleanup3', 'tearDownModule', 'cleanup1']) - - -class Test_TextTestRunner(unittest.TestCase): - """Tests for TextTestRunner.""" - - def setUp(self): - # clean the environment from pre-existing PYTHONWARNINGS to make - # test_warnings results consistent - self.pythonwarnings = os.environ.get('PYTHONWARNINGS') - if self.pythonwarnings: - del os.environ['PYTHONWARNINGS'] - - def tearDown(self): - # bring back pre-existing PYTHONWARNINGS if present - if self.pythonwarnings: - os.environ['PYTHONWARNINGS'] = self.pythonwarnings - - def test_init(self): - runner = unittest.TextTestRunner() - self.assertFalse(runner.failfast) - self.assertFalse(runner.buffer) - self.assertEqual(runner.verbosity, 1) - self.assertEqual(runner.warnings, None) - self.assertTrue(runner.descriptions) - self.assertEqual(runner.resultclass, unittest.TextTestResult) - self.assertFalse(runner.tb_locals) - - def test_multiple_inheritance(self): - class AResult(unittest.TestResult): - def __init__(self, stream, descriptions, verbosity): - super(AResult, self).__init__(stream, descriptions, verbosity) - - class ATextResult(unittest.TextTestResult, AResult): - pass - - # This used to raise an exception due to TextTestResult not passing - # on arguments in its __init__ super call - ATextResult(None, None, 1) - - def testBufferAndFailfast(self): - class Test(unittest.TestCase): - def testFoo(self): - pass - result = unittest.TestResult() - runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True, - buffer=True) - # Use our result object - runner._makeResult = lambda: result - runner.run(Test('testFoo')) - - self.assertTrue(result.failfast) - self.assertTrue(result.buffer) - - def test_locals(self): - runner = unittest.TextTestRunner(stream=io.StringIO(), tb_locals=True) - result = runner.run(unittest.TestSuite()) - self.assertEqual(True, result.tb_locals) - - def testRunnerRegistersResult(self): - class Test(unittest.TestCase): - def testFoo(self): - pass - originalRegisterResult = unittest.runner.registerResult - def cleanup(): - unittest.runner.registerResult = originalRegisterResult - self.addCleanup(cleanup) - - result = unittest.TestResult() - runner = unittest.TextTestRunner(stream=io.StringIO()) - # Use our result object - runner._makeResult = lambda: result - - self.wasRegistered = 0 - def fakeRegisterResult(thisResult): - self.wasRegistered += 1 - self.assertEqual(thisResult, result) - unittest.runner.registerResult = fakeRegisterResult - - runner.run(unittest.TestSuite()) - self.assertEqual(self.wasRegistered, 1) - - def test_works_with_result_without_startTestRun_stopTestRun(self): - class OldTextResult(ResultWithNoStartTestRunStopTestRun): - separator2 = '' - def printErrors(self): - pass - - class Runner(unittest.TextTestRunner): - def __init__(self): - super(Runner, self).__init__(io.StringIO()) - - def _makeResult(self): - return OldTextResult() - - runner = Runner() - runner.run(unittest.TestSuite()) - - def test_startTestRun_stopTestRun_called(self): - class LoggingTextResult(LoggingResult): - separator2 = '' - def printErrors(self): - pass - - class LoggingRunner(unittest.TextTestRunner): - def __init__(self, events): - super(LoggingRunner, self).__init__(io.StringIO()) - self._events = events - - def _makeResult(self): - return LoggingTextResult(self._events) - - events = [] - runner = LoggingRunner(events) - runner.run(unittest.TestSuite()) - expected = ['startTestRun', 'stopTestRun'] - self.assertEqual(events, expected) - - def test_pickle_unpickle(self): - # Issue #7197: a TextTestRunner should be (un)pickleable. This is - # required by test_multiprocessing under Windows (in verbose mode). - stream = io.StringIO("foo") - runner = unittest.TextTestRunner(stream) - for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(runner, protocol) - obj = pickle.loads(s) - # StringIO objects never compare equal, a cheap test instead. - self.assertEqual(obj.stream.getvalue(), stream.getvalue()) - - def test_resultclass(self): - def MockResultClass(*args): - return args - STREAM = object() - DESCRIPTIONS = object() - VERBOSITY = object() - runner = unittest.TextTestRunner(STREAM, DESCRIPTIONS, VERBOSITY, - resultclass=MockResultClass) - self.assertEqual(runner.resultclass, MockResultClass) - - expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY) - self.assertEqual(runner._makeResult(), expectedresult) - - def test_warnings(self): - """ - Check that warnings argument of TextTestRunner correctly affects the - behavior of the warnings. - """ - # see #10535 and the _test_warnings file for more information - - def get_parse_out_err(p): - return [b.splitlines() for b in p.communicate()] - opts = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, - cwd=os.path.dirname(__file__)) - ae_msg = b'Please use assertEqual instead.' - at_msg = b'Please use assertTrue instead.' - - # no args -> all the warnings are printed, unittest warnings only once - p = subprocess.Popen([sys.executable, '-E', '_test_warnings.py'], **opts) - with p: - out, err = get_parse_out_err(p) - self.assertIn(b'OK', err) - # check that the total number of warnings in the output is correct - self.assertEqual(len(out), 12) - # check that the numbers of the different kind of warnings is correct - for msg in [b'dw', b'iw', b'uw']: - self.assertEqual(out.count(msg), 3) - for msg in [ae_msg, at_msg, b'rw']: - self.assertEqual(out.count(msg), 1) - - args_list = ( - # passing 'ignore' as warnings arg -> no warnings - [sys.executable, '_test_warnings.py', 'ignore'], - # -W doesn't affect the result if the arg is passed - [sys.executable, '-Wa', '_test_warnings.py', 'ignore'], - # -W affects the result if the arg is not passed - [sys.executable, '-Wi', '_test_warnings.py'] - ) - # in all these cases no warnings are printed - for args in args_list: - p = subprocess.Popen(args, **opts) - with p: - out, err = get_parse_out_err(p) - self.assertIn(b'OK', err) - self.assertEqual(len(out), 0) - - - # passing 'always' as warnings arg -> all the warnings printed, - # unittest warnings only once - p = subprocess.Popen([sys.executable, '_test_warnings.py', 'always'], - **opts) - with p: - out, err = get_parse_out_err(p) - self.assertIn(b'OK', err) - self.assertEqual(len(out), 14) - for msg in [b'dw', b'iw', b'uw', b'rw']: - self.assertEqual(out.count(msg), 3) - for msg in [ae_msg, at_msg]: - self.assertEqual(out.count(msg), 1) - - def testStdErrLookedUpAtInstantiationTime(self): - # see issue 10786 - old_stderr = sys.stderr - f = io.StringIO() - sys.stderr = f - try: - runner = unittest.TextTestRunner() - self.assertTrue(runner.stream.stream is f) - finally: - sys.stderr = old_stderr - - def testSpecifiedStreamUsed(self): - # see issue 10786 - f = io.StringIO() - runner = unittest.TextTestRunner(f) - self.assertTrue(runner.stream.stream is f) - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_setups.py b/Monika After Story/game/python-packages/unittest/test/test_setups.py deleted file mode 100644 index 2df703ed93..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_setups.py +++ /dev/null @@ -1,507 +0,0 @@ -import io -import sys - -import unittest - - -def resultFactory(*_): - return unittest.TestResult() - - -class TestSetups(unittest.TestCase): - - def getRunner(self): - return unittest.TextTestRunner(resultclass=resultFactory, - stream=io.StringIO()) - def runTests(self, *cases): - suite = unittest.TestSuite() - for case in cases: - tests = unittest.defaultTestLoader.loadTestsFromTestCase(case) - suite.addTests(tests) - - runner = self.getRunner() - - # creating a nested suite exposes some potential bugs - realSuite = unittest.TestSuite() - realSuite.addTest(suite) - # adding empty suites to the end exposes potential bugs - suite.addTest(unittest.TestSuite()) - realSuite.addTest(unittest.TestSuite()) - return runner.run(realSuite) - - def test_setup_class(self): - class Test(unittest.TestCase): - setUpCalled = 0 - @classmethod - def setUpClass(cls): - Test.setUpCalled += 1 - unittest.TestCase.setUpClass() - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(Test) - - self.assertEqual(Test.setUpCalled, 1) - self.assertEqual(result.testsRun, 2) - self.assertEqual(len(result.errors), 0) - - def test_teardown_class(self): - class Test(unittest.TestCase): - tearDownCalled = 0 - @classmethod - def tearDownClass(cls): - Test.tearDownCalled += 1 - unittest.TestCase.tearDownClass() - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(Test) - - self.assertEqual(Test.tearDownCalled, 1) - self.assertEqual(result.testsRun, 2) - self.assertEqual(len(result.errors), 0) - - def test_teardown_class_two_classes(self): - class Test(unittest.TestCase): - tearDownCalled = 0 - @classmethod - def tearDownClass(cls): - Test.tearDownCalled += 1 - unittest.TestCase.tearDownClass() - def test_one(self): - pass - def test_two(self): - pass - - class Test2(unittest.TestCase): - tearDownCalled = 0 - @classmethod - def tearDownClass(cls): - Test2.tearDownCalled += 1 - unittest.TestCase.tearDownClass() - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(Test, Test2) - - self.assertEqual(Test.tearDownCalled, 1) - self.assertEqual(Test2.tearDownCalled, 1) - self.assertEqual(result.testsRun, 4) - self.assertEqual(len(result.errors), 0) - - def test_error_in_setupclass(self): - class BrokenTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - raise TypeError('foo') - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(BrokenTest) - - self.assertEqual(result.testsRun, 0) - self.assertEqual(len(result.errors), 1) - error, _ = result.errors[0] - self.assertEqual(str(error), - 'setUpClass (%s.%s)' % (__name__, BrokenTest.__qualname__)) - - def test_error_in_teardown_class(self): - class Test(unittest.TestCase): - tornDown = 0 - @classmethod - def tearDownClass(cls): - Test.tornDown += 1 - raise TypeError('foo') - def test_one(self): - pass - def test_two(self): - pass - - class Test2(unittest.TestCase): - tornDown = 0 - @classmethod - def tearDownClass(cls): - Test2.tornDown += 1 - raise TypeError('foo') - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(Test, Test2) - self.assertEqual(result.testsRun, 4) - self.assertEqual(len(result.errors), 2) - self.assertEqual(Test.tornDown, 1) - self.assertEqual(Test2.tornDown, 1) - - error, _ = result.errors[0] - self.assertEqual(str(error), - 'tearDownClass (%s.%s)' % (__name__, Test.__qualname__)) - - def test_class_not_torndown_when_setup_fails(self): - class Test(unittest.TestCase): - tornDown = False - @classmethod - def setUpClass(cls): - raise TypeError - @classmethod - def tearDownClass(cls): - Test.tornDown = True - raise TypeError('foo') - def test_one(self): - pass - - self.runTests(Test) - self.assertFalse(Test.tornDown) - - def test_class_not_setup_or_torndown_when_skipped(self): - class Test(unittest.TestCase): - classSetUp = False - tornDown = False - @classmethod - def setUpClass(cls): - Test.classSetUp = True - @classmethod - def tearDownClass(cls): - Test.tornDown = True - def test_one(self): - pass - - Test = unittest.skip("hop")(Test) - self.runTests(Test) - self.assertFalse(Test.classSetUp) - self.assertFalse(Test.tornDown) - - def test_setup_teardown_order_with_pathological_suite(self): - results = [] - - class Module1(object): - @staticmethod - def setUpModule(): - results.append('Module1.setUpModule') - @staticmethod - def tearDownModule(): - results.append('Module1.tearDownModule') - - class Module2(object): - @staticmethod - def setUpModule(): - results.append('Module2.setUpModule') - @staticmethod - def tearDownModule(): - results.append('Module2.tearDownModule') - - class Test1(unittest.TestCase): - @classmethod - def setUpClass(cls): - results.append('setup 1') - @classmethod - def tearDownClass(cls): - results.append('teardown 1') - def testOne(self): - results.append('Test1.testOne') - def testTwo(self): - results.append('Test1.testTwo') - - class Test2(unittest.TestCase): - @classmethod - def setUpClass(cls): - results.append('setup 2') - @classmethod - def tearDownClass(cls): - results.append('teardown 2') - def testOne(self): - results.append('Test2.testOne') - def testTwo(self): - results.append('Test2.testTwo') - - class Test3(unittest.TestCase): - @classmethod - def setUpClass(cls): - results.append('setup 3') - @classmethod - def tearDownClass(cls): - results.append('teardown 3') - def testOne(self): - results.append('Test3.testOne') - def testTwo(self): - results.append('Test3.testTwo') - - Test1.__module__ = Test2.__module__ = 'Module' - Test3.__module__ = 'Module2' - sys.modules['Module'] = Module1 - sys.modules['Module2'] = Module2 - - first = unittest.TestSuite((Test1('testOne'),)) - second = unittest.TestSuite((Test1('testTwo'),)) - third = unittest.TestSuite((Test2('testOne'),)) - fourth = unittest.TestSuite((Test2('testTwo'),)) - fifth = unittest.TestSuite((Test3('testOne'),)) - sixth = unittest.TestSuite((Test3('testTwo'),)) - suite = unittest.TestSuite((first, second, third, fourth, fifth, sixth)) - - runner = self.getRunner() - result = runner.run(suite) - self.assertEqual(result.testsRun, 6) - self.assertEqual(len(result.errors), 0) - - self.assertEqual(results, - ['Module1.setUpModule', 'setup 1', - 'Test1.testOne', 'Test1.testTwo', 'teardown 1', - 'setup 2', 'Test2.testOne', 'Test2.testTwo', - 'teardown 2', 'Module1.tearDownModule', - 'Module2.setUpModule', 'setup 3', - 'Test3.testOne', 'Test3.testTwo', - 'teardown 3', 'Module2.tearDownModule']) - - def test_setup_module(self): - class Module(object): - moduleSetup = 0 - @staticmethod - def setUpModule(): - Module.moduleSetup += 1 - - class Test(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - Test.__module__ = 'Module' - sys.modules['Module'] = Module - - result = self.runTests(Test) - self.assertEqual(Module.moduleSetup, 1) - self.assertEqual(result.testsRun, 2) - self.assertEqual(len(result.errors), 0) - - def test_error_in_setup_module(self): - class Module(object): - moduleSetup = 0 - moduleTornDown = 0 - @staticmethod - def setUpModule(): - Module.moduleSetup += 1 - raise TypeError('foo') - @staticmethod - def tearDownModule(): - Module.moduleTornDown += 1 - - class Test(unittest.TestCase): - classSetUp = False - classTornDown = False - @classmethod - def setUpClass(cls): - Test.classSetUp = True - @classmethod - def tearDownClass(cls): - Test.classTornDown = True - def test_one(self): - pass - def test_two(self): - pass - - class Test2(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - Test.__module__ = 'Module' - Test2.__module__ = 'Module' - sys.modules['Module'] = Module - - result = self.runTests(Test, Test2) - self.assertEqual(Module.moduleSetup, 1) - self.assertEqual(Module.moduleTornDown, 0) - self.assertEqual(result.testsRun, 0) - self.assertFalse(Test.classSetUp) - self.assertFalse(Test.classTornDown) - self.assertEqual(len(result.errors), 1) - error, _ = result.errors[0] - self.assertEqual(str(error), 'setUpModule (Module)') - - def test_testcase_with_missing_module(self): - class Test(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - Test.__module__ = 'Module' - sys.modules.pop('Module', None) - - result = self.runTests(Test) - self.assertEqual(result.testsRun, 2) - - def test_teardown_module(self): - class Module(object): - moduleTornDown = 0 - @staticmethod - def tearDownModule(): - Module.moduleTornDown += 1 - - class Test(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - Test.__module__ = 'Module' - sys.modules['Module'] = Module - - result = self.runTests(Test) - self.assertEqual(Module.moduleTornDown, 1) - self.assertEqual(result.testsRun, 2) - self.assertEqual(len(result.errors), 0) - - def test_error_in_teardown_module(self): - class Module(object): - moduleTornDown = 0 - @staticmethod - def tearDownModule(): - Module.moduleTornDown += 1 - raise TypeError('foo') - - class Test(unittest.TestCase): - classSetUp = False - classTornDown = False - @classmethod - def setUpClass(cls): - Test.classSetUp = True - @classmethod - def tearDownClass(cls): - Test.classTornDown = True - def test_one(self): - pass - def test_two(self): - pass - - class Test2(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - Test.__module__ = 'Module' - Test2.__module__ = 'Module' - sys.modules['Module'] = Module - - result = self.runTests(Test, Test2) - self.assertEqual(Module.moduleTornDown, 1) - self.assertEqual(result.testsRun, 4) - self.assertTrue(Test.classSetUp) - self.assertTrue(Test.classTornDown) - self.assertEqual(len(result.errors), 1) - error, _ = result.errors[0] - self.assertEqual(str(error), 'tearDownModule (Module)') - - def test_skiptest_in_setupclass(self): - class Test(unittest.TestCase): - @classmethod - def setUpClass(cls): - raise unittest.SkipTest('foo') - def test_one(self): - pass - def test_two(self): - pass - - result = self.runTests(Test) - self.assertEqual(result.testsRun, 0) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.skipped), 1) - skipped = result.skipped[0][0] - self.assertEqual(str(skipped), - 'setUpClass (%s.%s)' % (__name__, Test.__qualname__)) - - def test_skiptest_in_setupmodule(self): - class Test(unittest.TestCase): - def test_one(self): - pass - def test_two(self): - pass - - class Module(object): - @staticmethod - def setUpModule(): - raise unittest.SkipTest('foo') - - Test.__module__ = 'Module' - sys.modules['Module'] = Module - - result = self.runTests(Test) - self.assertEqual(result.testsRun, 0) - self.assertEqual(len(result.errors), 0) - self.assertEqual(len(result.skipped), 1) - skipped = result.skipped[0][0] - self.assertEqual(str(skipped), 'setUpModule (Module)') - - def test_suite_debug_executes_setups_and_teardowns(self): - ordering = [] - - class Module(object): - @staticmethod - def setUpModule(): - ordering.append('setUpModule') - @staticmethod - def tearDownModule(): - ordering.append('tearDownModule') - - class Test(unittest.TestCase): - @classmethod - def setUpClass(cls): - ordering.append('setUpClass') - @classmethod - def tearDownClass(cls): - ordering.append('tearDownClass') - def test_something(self): - ordering.append('test_something') - - Test.__module__ = 'Module' - sys.modules['Module'] = Module - - suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) - suite.debug() - expectedOrder = ['setUpModule', 'setUpClass', 'test_something', 'tearDownClass', 'tearDownModule'] - self.assertEqual(ordering, expectedOrder) - - def test_suite_debug_propagates_exceptions(self): - class Module(object): - @staticmethod - def setUpModule(): - if phase == 0: - raise Exception('setUpModule') - @staticmethod - def tearDownModule(): - if phase == 1: - raise Exception('tearDownModule') - - class Test(unittest.TestCase): - @classmethod - def setUpClass(cls): - if phase == 2: - raise Exception('setUpClass') - @classmethod - def tearDownClass(cls): - if phase == 3: - raise Exception('tearDownClass') - def test_something(self): - if phase == 4: - raise Exception('test_something') - - Test.__module__ = 'Module' - sys.modules['Module'] = Module - - messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') - for phase, msg in enumerate(messages): - _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) - suite = unittest.TestSuite([_suite]) - with self.assertRaisesRegex(Exception, msg): - suite.debug() - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_skipping.py b/Monika After Story/game/python-packages/unittest/test/test_skipping.py deleted file mode 100644 index 1c178a95f7..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_skipping.py +++ /dev/null @@ -1,271 +0,0 @@ -import unittest - -from unittest.test.support import LoggingResult - - -class Test_TestSkipping(unittest.TestCase): - - def test_skipping(self): - class Foo(unittest.TestCase): - def test_skip_me(self): - self.skipTest("skip") - events = [] - result = LoggingResult(events) - test = Foo("test_skip_me") - test.run(result) - self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) - self.assertEqual(result.skipped, [(test, "skip")]) - - # Try letting setUp skip the test now. - class Foo(unittest.TestCase): - def setUp(self): - self.skipTest("testing") - def test_nothing(self): pass - events = [] - result = LoggingResult(events) - test = Foo("test_nothing") - test.run(result) - self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) - self.assertEqual(result.skipped, [(test, "testing")]) - self.assertEqual(result.testsRun, 1) - - def test_skipping_subtests(self): - class Foo(unittest.TestCase): - def test_skip_me(self): - with self.subTest(a=1): - with self.subTest(b=2): - self.skipTest("skip 1") - self.skipTest("skip 2") - self.skipTest("skip 3") - events = [] - result = LoggingResult(events) - test = Foo("test_skip_me") - test.run(result) - self.assertEqual(events, ['startTest', 'addSkip', 'addSkip', - 'addSkip', 'stopTest']) - self.assertEqual(len(result.skipped), 3) - subtest, msg = result.skipped[0] - self.assertEqual(msg, "skip 1") - self.assertIsInstance(subtest, unittest.TestCase) - self.assertIsNot(subtest, test) - subtest, msg = result.skipped[1] - self.assertEqual(msg, "skip 2") - self.assertIsInstance(subtest, unittest.TestCase) - self.assertIsNot(subtest, test) - self.assertEqual(result.skipped[2], (test, "skip 3")) - - def test_skipping_decorators(self): - op_table = ((unittest.skipUnless, False, True), - (unittest.skipIf, True, False)) - for deco, do_skip, dont_skip in op_table: - class Foo(unittest.TestCase): - @deco(do_skip, "testing") - def test_skip(self): pass - - @deco(dont_skip, "testing") - def test_dont_skip(self): pass - test_do_skip = Foo("test_skip") - test_dont_skip = Foo("test_dont_skip") - suite = unittest.TestSuite([test_do_skip, test_dont_skip]) - events = [] - result = LoggingResult(events) - suite.run(result) - self.assertEqual(len(result.skipped), 1) - expected = ['startTest', 'addSkip', 'stopTest', - 'startTest', 'addSuccess', 'stopTest'] - self.assertEqual(events, expected) - self.assertEqual(result.testsRun, 2) - self.assertEqual(result.skipped, [(test_do_skip, "testing")]) - self.assertTrue(result.wasSuccessful()) - - def test_skip_class(self): - @unittest.skip("testing") - class Foo(unittest.TestCase): - def test_1(self): - record.append(1) - record = [] - result = unittest.TestResult() - test = Foo("test_1") - suite = unittest.TestSuite([test]) - suite.run(result) - self.assertEqual(result.skipped, [(test, "testing")]) - self.assertEqual(record, []) - - def test_skip_non_unittest_class(self): - @unittest.skip("testing") - class Mixin: - def test_1(self): - record.append(1) - class Foo(Mixin, unittest.TestCase): - pass - record = [] - result = unittest.TestResult() - test = Foo("test_1") - suite = unittest.TestSuite([test]) - suite.run(result) - self.assertEqual(result.skipped, [(test, "testing")]) - self.assertEqual(record, []) - - def test_expected_failure(self): - class Foo(unittest.TestCase): - @unittest.expectedFailure - def test_die(self): - self.fail("help me!") - events = [] - result = LoggingResult(events) - test = Foo("test_die") - test.run(result) - self.assertEqual(events, - ['startTest', 'addExpectedFailure', 'stopTest']) - self.assertEqual(result.expectedFailures[0][0], test) - self.assertTrue(result.wasSuccessful()) - - def test_expected_failure_with_wrapped_class(self): - @unittest.expectedFailure - class Foo(unittest.TestCase): - def test_1(self): - self.assertTrue(False) - - events = [] - result = LoggingResult(events) - test = Foo("test_1") - test.run(result) - self.assertEqual(events, - ['startTest', 'addExpectedFailure', 'stopTest']) - self.assertEqual(result.expectedFailures[0][0], test) - self.assertTrue(result.wasSuccessful()) - - def test_expected_failure_with_wrapped_subclass(self): - class Foo(unittest.TestCase): - def test_1(self): - self.assertTrue(False) - - @unittest.expectedFailure - class Bar(Foo): - pass - - events = [] - result = LoggingResult(events) - test = Bar("test_1") - test.run(result) - self.assertEqual(events, - ['startTest', 'addExpectedFailure', 'stopTest']) - self.assertEqual(result.expectedFailures[0][0], test) - self.assertTrue(result.wasSuccessful()) - - def test_expected_failure_subtests(self): - # A failure in any subtest counts as the expected failure of the - # whole test. - class Foo(unittest.TestCase): - @unittest.expectedFailure - def test_die(self): - with self.subTest(): - # This one succeeds - pass - with self.subTest(): - self.fail("help me!") - with self.subTest(): - # This one doesn't get executed - self.fail("shouldn't come here") - events = [] - result = LoggingResult(events) - test = Foo("test_die") - test.run(result) - self.assertEqual(events, - ['startTest', 'addSubTestSuccess', - 'addExpectedFailure', 'stopTest']) - self.assertEqual(len(result.expectedFailures), 1) - self.assertIs(result.expectedFailures[0][0], test) - self.assertTrue(result.wasSuccessful()) - - def test_unexpected_success(self): - class Foo(unittest.TestCase): - @unittest.expectedFailure - def test_die(self): - pass - events = [] - result = LoggingResult(events) - test = Foo("test_die") - test.run(result) - self.assertEqual(events, - ['startTest', 'addUnexpectedSuccess', 'stopTest']) - self.assertFalse(result.failures) - self.assertEqual(result.unexpectedSuccesses, [test]) - self.assertFalse(result.wasSuccessful()) - - def test_unexpected_success_subtests(self): - # Success in all subtests counts as the unexpected success of - # the whole test. - class Foo(unittest.TestCase): - @unittest.expectedFailure - def test_die(self): - with self.subTest(): - # This one succeeds - pass - with self.subTest(): - # So does this one - pass - events = [] - result = LoggingResult(events) - test = Foo("test_die") - test.run(result) - self.assertEqual(events, - ['startTest', - 'addSubTestSuccess', 'addSubTestSuccess', - 'addUnexpectedSuccess', 'stopTest']) - self.assertFalse(result.failures) - self.assertEqual(result.unexpectedSuccesses, [test]) - self.assertFalse(result.wasSuccessful()) - - def test_skip_doesnt_run_setup(self): - class Foo(unittest.TestCase): - wasSetUp = False - wasTornDown = False - def setUp(self): - Foo.wasSetUp = True - def tornDown(self): - Foo.wasTornDown = True - @unittest.skip('testing') - def test_1(self): - pass - - result = unittest.TestResult() - test = Foo("test_1") - suite = unittest.TestSuite([test]) - suite.run(result) - self.assertEqual(result.skipped, [(test, "testing")]) - self.assertFalse(Foo.wasSetUp) - self.assertFalse(Foo.wasTornDown) - - def test_decorated_skip(self): - def decorator(func): - def inner(*a): - return func(*a) - return inner - - class Foo(unittest.TestCase): - @decorator - @unittest.skip('testing') - def test_1(self): - pass - - result = unittest.TestResult() - test = Foo("test_1") - suite = unittest.TestSuite([test]) - suite.run(result) - self.assertEqual(result.skipped, [(test, "testing")]) - - def test_skip_without_reason(self): - class Foo(unittest.TestCase): - @unittest.skip - def test_1(self): - pass - - result = unittest.TestResult() - test = Foo("test_1") - suite = unittest.TestSuite([test]) - suite.run(result) - self.assertEqual(result.skipped, [(test, "")]) - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/test_suite.py b/Monika After Story/game/python-packages/unittest/test/test_suite.py deleted file mode 100644 index 0551a16996..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/test_suite.py +++ /dev/null @@ -1,447 +0,0 @@ -import unittest - -import gc -import sys -import weakref -from unittest.test.support import LoggingResult, TestEquality - - -### Support code for Test_TestSuite -################################################################ - -class Test(object): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - def test_3(self): pass - def runTest(self): pass - -def _mk_TestSuite(*names): - return unittest.TestSuite(Test.Foo(n) for n in names) - -################################################################ - - -class Test_TestSuite(unittest.TestCase, TestEquality): - - ### Set up attributes needed by inherited tests - ################################################################ - - # Used by TestEquality.test_eq - eq_pairs = [(unittest.TestSuite(), unittest.TestSuite()) - ,(unittest.TestSuite(), unittest.TestSuite([])) - ,(_mk_TestSuite('test_1'), _mk_TestSuite('test_1'))] - - # Used by TestEquality.test_ne - ne_pairs = [(unittest.TestSuite(), _mk_TestSuite('test_1')) - ,(unittest.TestSuite([]), _mk_TestSuite('test_1')) - ,(_mk_TestSuite('test_1', 'test_2'), _mk_TestSuite('test_1', 'test_3')) - ,(_mk_TestSuite('test_1'), _mk_TestSuite('test_2'))] - - ################################################################ - ### /Set up attributes needed by inherited tests - - ### Tests for TestSuite.__init__ - ################################################################ - - # "class TestSuite([tests])" - # - # The tests iterable should be optional - def test_init__tests_optional(self): - suite = unittest.TestSuite() - - self.assertEqual(suite.countTestCases(), 0) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 0) - - # "class TestSuite([tests])" - # ... - # "If tests is given, it must be an iterable of individual test cases - # or other test suites that will be used to build the suite initially" - # - # TestSuite should deal with empty tests iterables by allowing the - # creation of an empty suite - def test_init__empty_tests(self): - suite = unittest.TestSuite([]) - - self.assertEqual(suite.countTestCases(), 0) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 0) - - # "class TestSuite([tests])" - # ... - # "If tests is given, it must be an iterable of individual test cases - # or other test suites that will be used to build the suite initially" - # - # TestSuite should allow any iterable to provide tests - def test_init__tests_from_any_iterable(self): - def tests(): - yield unittest.FunctionTestCase(lambda: None) - yield unittest.FunctionTestCase(lambda: None) - - suite_1 = unittest.TestSuite(tests()) - self.assertEqual(suite_1.countTestCases(), 2) - - suite_2 = unittest.TestSuite(suite_1) - self.assertEqual(suite_2.countTestCases(), 2) - - suite_3 = unittest.TestSuite(set(suite_1)) - self.assertEqual(suite_3.countTestCases(), 2) - - # countTestCases() still works after tests are run - suite_1.run(unittest.TestResult()) - self.assertEqual(suite_1.countTestCases(), 2) - suite_2.run(unittest.TestResult()) - self.assertEqual(suite_2.countTestCases(), 2) - suite_3.run(unittest.TestResult()) - self.assertEqual(suite_3.countTestCases(), 2) - - # "class TestSuite([tests])" - # ... - # "If tests is given, it must be an iterable of individual test cases - # or other test suites that will be used to build the suite initially" - # - # Does TestSuite() also allow other TestSuite() instances to be present - # in the tests iterable? - def test_init__TestSuite_instances_in_tests(self): - def tests(): - ftc = unittest.FunctionTestCase(lambda: None) - yield unittest.TestSuite([ftc]) - yield unittest.FunctionTestCase(lambda: None) - - suite = unittest.TestSuite(tests()) - self.assertEqual(suite.countTestCases(), 2) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 2) - - ################################################################ - ### /Tests for TestSuite.__init__ - - # Container types should support the iter protocol - def test_iter(self): - test1 = unittest.FunctionTestCase(lambda: None) - test2 = unittest.FunctionTestCase(lambda: None) - suite = unittest.TestSuite((test1, test2)) - - self.assertEqual(list(suite), [test1, test2]) - - # "Return the number of tests represented by the this test object. - # ...this method is also implemented by the TestSuite class, which can - # return larger [greater than 1] values" - # - # Presumably an empty TestSuite returns 0? - def test_countTestCases_zero_simple(self): - suite = unittest.TestSuite() - - self.assertEqual(suite.countTestCases(), 0) - - # "Return the number of tests represented by the this test object. - # ...this method is also implemented by the TestSuite class, which can - # return larger [greater than 1] values" - # - # Presumably an empty TestSuite (even if it contains other empty - # TestSuite instances) returns 0? - def test_countTestCases_zero_nested(self): - class Test1(unittest.TestCase): - def test(self): - pass - - suite = unittest.TestSuite([unittest.TestSuite()]) - - self.assertEqual(suite.countTestCases(), 0) - - # "Return the number of tests represented by the this test object. - # ...this method is also implemented by the TestSuite class, which can - # return larger [greater than 1] values" - def test_countTestCases_simple(self): - test1 = unittest.FunctionTestCase(lambda: None) - test2 = unittest.FunctionTestCase(lambda: None) - suite = unittest.TestSuite((test1, test2)) - - self.assertEqual(suite.countTestCases(), 2) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 2) - - # "Return the number of tests represented by the this test object. - # ...this method is also implemented by the TestSuite class, which can - # return larger [greater than 1] values" - # - # Make sure this holds for nested TestSuite instances, too - def test_countTestCases_nested(self): - class Test1(unittest.TestCase): - def test1(self): pass - def test2(self): pass - - test2 = unittest.FunctionTestCase(lambda: None) - test3 = unittest.FunctionTestCase(lambda: None) - child = unittest.TestSuite((Test1('test2'), test2)) - parent = unittest.TestSuite((test3, child, Test1('test1'))) - - self.assertEqual(parent.countTestCases(), 4) - # countTestCases() still works after tests are run - parent.run(unittest.TestResult()) - self.assertEqual(parent.countTestCases(), 4) - self.assertEqual(child.countTestCases(), 2) - - # "Run the tests associated with this suite, collecting the result into - # the test result object passed as result." - # - # And if there are no tests? What then? - def test_run__empty_suite(self): - events = [] - result = LoggingResult(events) - - suite = unittest.TestSuite() - - suite.run(result) - - self.assertEqual(events, []) - - # "Note that unlike TestCase.run(), TestSuite.run() requires the - # "result object to be passed in." - def test_run__requires_result(self): - suite = unittest.TestSuite() - - try: - suite.run() - except TypeError: - pass - else: - self.fail("Failed to raise TypeError") - - # "Run the tests associated with this suite, collecting the result into - # the test result object passed as result." - def test_run(self): - events = [] - result = LoggingResult(events) - - class LoggingCase(unittest.TestCase): - def run(self, result): - events.append('run %s' % self._testMethodName) - - def test1(self): pass - def test2(self): pass - - tests = [LoggingCase('test1'), LoggingCase('test2')] - - unittest.TestSuite(tests).run(result) - - self.assertEqual(events, ['run test1', 'run test2']) - - # "Add a TestCase ... to the suite" - def test_addTest__TestCase(self): - class Foo(unittest.TestCase): - def test(self): pass - - test = Foo('test') - suite = unittest.TestSuite() - - suite.addTest(test) - - self.assertEqual(suite.countTestCases(), 1) - self.assertEqual(list(suite), [test]) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 1) - - # "Add a ... TestSuite to the suite" - def test_addTest__TestSuite(self): - class Foo(unittest.TestCase): - def test(self): pass - - suite_2 = unittest.TestSuite([Foo('test')]) - - suite = unittest.TestSuite() - suite.addTest(suite_2) - - self.assertEqual(suite.countTestCases(), 1) - self.assertEqual(list(suite), [suite_2]) - # countTestCases() still works after tests are run - suite.run(unittest.TestResult()) - self.assertEqual(suite.countTestCases(), 1) - - # "Add all the tests from an iterable of TestCase and TestSuite - # instances to this test suite." - # - # "This is equivalent to iterating over tests, calling addTest() for - # each element" - def test_addTests(self): - class Foo(unittest.TestCase): - def test_1(self): pass - def test_2(self): pass - - test_1 = Foo('test_1') - test_2 = Foo('test_2') - inner_suite = unittest.TestSuite([test_2]) - - def gen(): - yield test_1 - yield test_2 - yield inner_suite - - suite_1 = unittest.TestSuite() - suite_1.addTests(gen()) - - self.assertEqual(list(suite_1), list(gen())) - - # "This is equivalent to iterating over tests, calling addTest() for - # each element" - suite_2 = unittest.TestSuite() - for t in gen(): - suite_2.addTest(t) - - self.assertEqual(suite_1, suite_2) - - # "Add all the tests from an iterable of TestCase and TestSuite - # instances to this test suite." - # - # What happens if it doesn't get an iterable? - def test_addTest__noniterable(self): - suite = unittest.TestSuite() - - try: - suite.addTests(5) - except TypeError: - pass - else: - self.fail("Failed to raise TypeError") - - def test_addTest__noncallable(self): - suite = unittest.TestSuite() - self.assertRaises(TypeError, suite.addTest, 5) - - def test_addTest__casesuiteclass(self): - suite = unittest.TestSuite() - self.assertRaises(TypeError, suite.addTest, Test_TestSuite) - self.assertRaises(TypeError, suite.addTest, unittest.TestSuite) - - def test_addTests__string(self): - suite = unittest.TestSuite() - self.assertRaises(TypeError, suite.addTests, "foo") - - def test_function_in_suite(self): - def f(_): - pass - suite = unittest.TestSuite() - suite.addTest(f) - - # when the bug is fixed this line will not crash - suite.run(unittest.TestResult()) - - def test_remove_test_at_index(self): - if not unittest.BaseTestSuite._cleanup: - raise unittest.SkipTest("Suite cleanup is disabled") - - suite = unittest.TestSuite() - - suite._tests = [1, 2, 3] - suite._removeTestAtIndex(1) - - self.assertEqual([1, None, 3], suite._tests) - - def test_remove_test_at_index_not_indexable(self): - if not unittest.BaseTestSuite._cleanup: - raise unittest.SkipTest("Suite cleanup is disabled") - - suite = unittest.TestSuite() - suite._tests = None - - # if _removeAtIndex raises for noniterables this next line will break - suite._removeTestAtIndex(2) - - def assert_garbage_collect_test_after_run(self, TestSuiteClass): - if not unittest.BaseTestSuite._cleanup: - raise unittest.SkipTest("Suite cleanup is disabled") - - class Foo(unittest.TestCase): - def test_nothing(self): - pass - - test = Foo('test_nothing') - wref = weakref.ref(test) - - suite = TestSuiteClass([wref()]) - suite.run(unittest.TestResult()) - - del test - - # for the benefit of non-reference counting implementations - gc.collect() - - self.assertEqual(suite._tests, [None]) - self.assertIsNone(wref()) - - def test_garbage_collect_test_after_run_BaseTestSuite(self): - self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite) - - def test_garbage_collect_test_after_run_TestSuite(self): - self.assert_garbage_collect_test_after_run(unittest.TestSuite) - - def test_basetestsuite(self): - class Test(unittest.TestCase): - wasSetUp = False - wasTornDown = False - @classmethod - def setUpClass(cls): - cls.wasSetUp = True - @classmethod - def tearDownClass(cls): - cls.wasTornDown = True - def testPass(self): - pass - def testFail(self): - fail - class Module(object): - wasSetUp = False - wasTornDown = False - @staticmethod - def setUpModule(): - Module.wasSetUp = True - @staticmethod - def tearDownModule(): - Module.wasTornDown = True - - Test.__module__ = 'Module' - sys.modules['Module'] = Module - self.addCleanup(sys.modules.pop, 'Module') - - suite = unittest.BaseTestSuite() - suite.addTests([Test('testPass'), Test('testFail')]) - self.assertEqual(suite.countTestCases(), 2) - - result = unittest.TestResult() - suite.run(result) - self.assertFalse(Module.wasSetUp) - self.assertFalse(Module.wasTornDown) - self.assertFalse(Test.wasSetUp) - self.assertFalse(Test.wasTornDown) - self.assertEqual(len(result.errors), 1) - self.assertEqual(len(result.failures), 0) - self.assertEqual(result.testsRun, 2) - self.assertEqual(suite.countTestCases(), 2) - - - def test_overriding_call(self): - class MySuite(unittest.TestSuite): - called = False - def __call__(self, *args, **kw): - self.called = True - unittest.TestSuite.__call__(self, *args, **kw) - - suite = MySuite() - result = unittest.TestResult() - wrapper = unittest.TestSuite() - wrapper.addTest(suite) - wrapper(result) - self.assertTrue(suite.called) - - # reusing results should be permitted even if abominable - self.assertFalse(result._testRunEntered) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py b/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py deleted file mode 100644 index 87d7ae994d..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -import sys -import unittest - - -here = os.path.dirname(__file__) -loader = unittest.defaultTestLoader - -def load_tests(*args): - suite = unittest.TestSuite() - for fn in os.listdir(here): - if fn.startswith("test") and fn.endswith(".py"): - modname = "unittest.test.testmock." + fn[:-3] - __import__(modname) - module = sys.modules[modname] - suite.addTest(loader.loadTestsFromModule(module)) - return suite diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py b/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py deleted file mode 100644 index 45c633a4ee..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/__main__.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import unittest - - -def load_tests(loader, standard_tests, pattern): - # top level directory cached on loader instance - this_dir = os.path.dirname(__file__) - pattern = pattern or "test*.py" - # We are inside unittest.test.testmock, so the top-level is three notches up - top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir))) - package_tests = loader.discover(start_dir=this_dir, pattern=pattern, - top_level_dir=top_level_dir) - standard_tests.addTests(package_tests) - return standard_tests - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/support.py b/Monika After Story/game/python-packages/unittest/test/testmock/support.py deleted file mode 100644 index 49986d65dc..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/support.py +++ /dev/null @@ -1,16 +0,0 @@ -target = {'foo': 'FOO'} - - -def is_instance(obj, klass): - """Version of is_instance that doesn't access __class__""" - return issubclass(type(obj), klass) - - -class SomeClass(object): - class_attribute = None - - def wibble(self): pass - - -class X(object): - pass diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py b/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py deleted file mode 100644 index 690ca4f55f..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testasync.py +++ /dev/null @@ -1,1059 +0,0 @@ -import asyncio -import gc -import inspect -import re -import unittest -from contextlib import contextmanager - -from asyncio import run, iscoroutinefunction -from unittest import IsolatedAsyncioTestCase -from unittest.mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, - create_autospec, sentinel, _CallList) - - -def tearDownModule(): - asyncio.set_event_loop_policy(None) - - -class AsyncClass: - def __init__(self): pass - async def async_method(self): pass - def normal_method(self): pass - - @classmethod - async def async_class_method(cls): pass - - @staticmethod - async def async_static_method(): pass - - -class AwaitableClass: - def __await__(self): yield - -async def async_func(): pass - -async def async_func_args(a, b, *, c): pass - -def normal_func(): pass - -class NormalClass(object): - def a(self): pass - - -async_foo_name = f'{__name__}.AsyncClass' -normal_foo_name = f'{__name__}.NormalClass' - - -@contextmanager -def assertNeverAwaited(test): - with test.assertWarnsRegex(RuntimeWarning, "was never awaited$"): - yield - # In non-CPython implementations of Python, this is needed because timely - # deallocation is not guaranteed by the garbage collector. - gc.collect() - - -class AsyncPatchDecoratorTest(unittest.TestCase): - def test_is_coroutine_function_patch(self): - @patch.object(AsyncClass, 'async_method') - def test_async(mock_method): - self.assertTrue(iscoroutinefunction(mock_method)) - test_async() - - def test_is_async_patch(self): - @patch.object(AsyncClass, 'async_method') - def test_async(mock_method): - m = mock_method() - self.assertTrue(inspect.isawaitable(m)) - run(m) - - @patch(f'{async_foo_name}.async_method') - def test_no_parent_attribute(mock_method): - m = mock_method() - self.assertTrue(inspect.isawaitable(m)) - run(m) - - test_async() - test_no_parent_attribute() - - def test_is_AsyncMock_patch(self): - @patch.object(AsyncClass, 'async_method') - def test_async(mock_method): - self.assertIsInstance(mock_method, AsyncMock) - - test_async() - - def test_is_AsyncMock_patch_staticmethod(self): - @patch.object(AsyncClass, 'async_static_method') - def test_async(mock_method): - self.assertIsInstance(mock_method, AsyncMock) - - test_async() - - def test_is_AsyncMock_patch_classmethod(self): - @patch.object(AsyncClass, 'async_class_method') - def test_async(mock_method): - self.assertIsInstance(mock_method, AsyncMock) - - test_async() - - def test_async_def_patch(self): - @patch(f"{__name__}.async_func", return_value=1) - @patch(f"{__name__}.async_func_args", return_value=2) - async def test_async(func_args_mock, func_mock): - self.assertEqual(func_args_mock._mock_name, "async_func_args") - self.assertEqual(func_mock._mock_name, "async_func") - - self.assertIsInstance(async_func, AsyncMock) - self.assertIsInstance(async_func_args, AsyncMock) - - self.assertEqual(await async_func(), 1) - self.assertEqual(await async_func_args(1, 2, c=3), 2) - - run(test_async()) - self.assertTrue(inspect.iscoroutinefunction(async_func)) - - -class AsyncPatchCMTest(unittest.TestCase): - def test_is_async_function_cm(self): - def test_async(): - with patch.object(AsyncClass, 'async_method') as mock_method: - self.assertTrue(iscoroutinefunction(mock_method)) - - test_async() - - def test_is_async_cm(self): - def test_async(): - with patch.object(AsyncClass, 'async_method') as mock_method: - m = mock_method() - self.assertTrue(inspect.isawaitable(m)) - run(m) - - test_async() - - def test_is_AsyncMock_cm(self): - def test_async(): - with patch.object(AsyncClass, 'async_method') as mock_method: - self.assertIsInstance(mock_method, AsyncMock) - - test_async() - - def test_async_def_cm(self): - async def test_async(): - with patch(f"{__name__}.async_func", AsyncMock()): - self.assertIsInstance(async_func, AsyncMock) - self.assertTrue(inspect.iscoroutinefunction(async_func)) - - run(test_async()) - - -class AsyncMockTest(unittest.TestCase): - def test_iscoroutinefunction_default(self): - mock = AsyncMock() - self.assertTrue(iscoroutinefunction(mock)) - - def test_iscoroutinefunction_function(self): - async def foo(): pass - mock = AsyncMock(foo) - self.assertTrue(iscoroutinefunction(mock)) - self.assertTrue(inspect.iscoroutinefunction(mock)) - - def test_isawaitable(self): - mock = AsyncMock() - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) - self.assertIn('assert_awaited', dir(mock)) - - def test_iscoroutinefunction_normal_function(self): - def foo(): pass - mock = AsyncMock(foo) - self.assertTrue(iscoroutinefunction(mock)) - self.assertTrue(inspect.iscoroutinefunction(mock)) - - def test_future_isfuture(self): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - fut = asyncio.Future() - loop.stop() - loop.close() - mock = AsyncMock(fut) - self.assertIsInstance(mock, asyncio.Future) - - -class AsyncAutospecTest(unittest.TestCase): - def test_is_AsyncMock_patch(self): - @patch(async_foo_name, autospec=True) - def test_async(mock_method): - self.assertIsInstance(mock_method.async_method, AsyncMock) - self.assertIsInstance(mock_method, MagicMock) - - @patch(async_foo_name, autospec=True) - def test_normal_method(mock_method): - self.assertIsInstance(mock_method.normal_method, MagicMock) - - test_async() - test_normal_method() - - def test_create_autospec_instance(self): - with self.assertRaises(RuntimeError): - create_autospec(async_func, instance=True) - - def test_create_autospec_awaitable_class(self): - awaitable_mock = create_autospec(spec=AwaitableClass()) - self.assertIsInstance(create_autospec(awaitable_mock), AsyncMock) - - def test_create_autospec(self): - spec = create_autospec(async_func_args) - awaitable = spec(1, 2, c=3) - async def main(): - await awaitable - - self.assertEqual(spec.await_count, 0) - self.assertIsNone(spec.await_args) - self.assertEqual(spec.await_args_list, []) - spec.assert_not_awaited() - - run(main()) - - self.assertTrue(iscoroutinefunction(spec)) - self.assertTrue(asyncio.iscoroutine(awaitable)) - self.assertEqual(spec.await_count, 1) - self.assertEqual(spec.await_args, call(1, 2, c=3)) - self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) - spec.assert_awaited_once() - spec.assert_awaited_once_with(1, 2, c=3) - spec.assert_awaited_with(1, 2, c=3) - spec.assert_awaited() - - with self.assertRaises(AssertionError): - spec.assert_any_await(e=1) - - - def test_patch_with_autospec(self): - - async def test_async(): - with patch(f"{__name__}.async_func_args", autospec=True) as mock_method: - awaitable = mock_method(1, 2, c=3) - self.assertIsInstance(mock_method.mock, AsyncMock) - - self.assertTrue(iscoroutinefunction(mock_method)) - self.assertTrue(asyncio.iscoroutine(awaitable)) - self.assertTrue(inspect.isawaitable(awaitable)) - - # Verify the default values during mock setup - self.assertEqual(mock_method.await_count, 0) - self.assertEqual(mock_method.await_args_list, []) - self.assertIsNone(mock_method.await_args) - mock_method.assert_not_awaited() - - await awaitable - - self.assertEqual(mock_method.await_count, 1) - self.assertEqual(mock_method.await_args, call(1, 2, c=3)) - self.assertEqual(mock_method.await_args_list, [call(1, 2, c=3)]) - mock_method.assert_awaited_once() - mock_method.assert_awaited_once_with(1, 2, c=3) - mock_method.assert_awaited_with(1, 2, c=3) - mock_method.assert_awaited() - - mock_method.reset_mock() - self.assertEqual(mock_method.await_count, 0) - self.assertIsNone(mock_method.await_args) - self.assertEqual(mock_method.await_args_list, []) - - run(test_async()) - - -class AsyncSpecTest(unittest.TestCase): - def test_spec_normal_methods_on_class(self): - def inner_test(mock_type): - mock = mock_type(AsyncClass) - self.assertIsInstance(mock.async_method, AsyncMock) - self.assertIsInstance(mock.normal_method, MagicMock) - - for mock_type in [AsyncMock, MagicMock]: - with self.subTest(f"test method types with {mock_type}"): - inner_test(mock_type) - - def test_spec_normal_methods_on_class_with_mock(self): - mock = Mock(AsyncClass) - self.assertIsInstance(mock.async_method, AsyncMock) - self.assertIsInstance(mock.normal_method, Mock) - - def test_spec_mock_type_kw(self): - def inner_test(mock_type): - async_mock = mock_type(spec=async_func) - self.assertIsInstance(async_mock, mock_type) - with assertNeverAwaited(self): - self.assertTrue(inspect.isawaitable(async_mock())) - - sync_mock = mock_type(spec=normal_func) - self.assertIsInstance(sync_mock, mock_type) - - for mock_type in [AsyncMock, MagicMock, Mock]: - with self.subTest(f"test spec kwarg with {mock_type}"): - inner_test(mock_type) - - def test_spec_mock_type_positional(self): - def inner_test(mock_type): - async_mock = mock_type(async_func) - self.assertIsInstance(async_mock, mock_type) - with assertNeverAwaited(self): - self.assertTrue(inspect.isawaitable(async_mock())) - - sync_mock = mock_type(normal_func) - self.assertIsInstance(sync_mock, mock_type) - - for mock_type in [AsyncMock, MagicMock, Mock]: - with self.subTest(f"test spec positional with {mock_type}"): - inner_test(mock_type) - - def test_spec_as_normal_kw_AsyncMock(self): - mock = AsyncMock(spec=normal_func) - self.assertIsInstance(mock, AsyncMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) - - def test_spec_as_normal_positional_AsyncMock(self): - mock = AsyncMock(normal_func) - self.assertIsInstance(mock, AsyncMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) - - def test_spec_async_mock(self): - @patch.object(AsyncClass, 'async_method', spec=True) - def test_async(mock_method): - self.assertIsInstance(mock_method, AsyncMock) - - test_async() - - def test_spec_parent_not_async_attribute_is(self): - @patch(async_foo_name, spec=True) - def test_async(mock_method): - self.assertIsInstance(mock_method, MagicMock) - self.assertIsInstance(mock_method.async_method, AsyncMock) - - test_async() - - def test_target_async_spec_not(self): - @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) - def test_async_attribute(mock_method): - self.assertIsInstance(mock_method, MagicMock) - self.assertFalse(inspect.iscoroutine(mock_method)) - self.assertFalse(inspect.isawaitable(mock_method)) - - test_async_attribute() - - def test_target_not_async_spec_is(self): - @patch.object(NormalClass, 'a', spec=async_func) - def test_attribute_not_async_spec_is(mock_async_func): - self.assertIsInstance(mock_async_func, AsyncMock) - test_attribute_not_async_spec_is() - - def test_spec_async_attributes(self): - @patch(normal_foo_name, spec=AsyncClass) - def test_async_attributes_coroutines(MockNormalClass): - self.assertIsInstance(MockNormalClass.async_method, AsyncMock) - self.assertIsInstance(MockNormalClass, MagicMock) - - test_async_attributes_coroutines() - - -class AsyncSpecSetTest(unittest.TestCase): - def test_is_AsyncMock_patch(self): - @patch.object(AsyncClass, 'async_method', spec_set=True) - def test_async(async_method): - self.assertIsInstance(async_method, AsyncMock) - test_async() - - def test_is_async_AsyncMock(self): - mock = AsyncMock(spec_set=AsyncClass.async_method) - self.assertTrue(iscoroutinefunction(mock)) - self.assertIsInstance(mock, AsyncMock) - - def test_is_child_AsyncMock(self): - mock = MagicMock(spec_set=AsyncClass) - self.assertTrue(iscoroutinefunction(mock.async_method)) - self.assertFalse(iscoroutinefunction(mock.normal_method)) - self.assertIsInstance(mock.async_method, AsyncMock) - self.assertIsInstance(mock.normal_method, MagicMock) - self.assertIsInstance(mock, MagicMock) - - def test_magicmock_lambda_spec(self): - mock_obj = MagicMock() - mock_obj.mock_func = MagicMock(spec=lambda x: x) - - with patch.object(mock_obj, "mock_func") as cm: - self.assertIsInstance(cm, MagicMock) - - -class AsyncArguments(IsolatedAsyncioTestCase): - async def test_add_return_value(self): - async def addition(self, var): pass - - mock = AsyncMock(addition, return_value=10) - output = await mock(5) - - self.assertEqual(output, 10) - - async def test_add_side_effect_exception(self): - async def addition(var): pass - mock = AsyncMock(addition, side_effect=Exception('err')) - with self.assertRaises(Exception): - await mock(5) - - async def test_add_side_effect_coroutine(self): - async def addition(var): - return var + 1 - mock = AsyncMock(side_effect=addition) - result = await mock(5) - self.assertEqual(result, 6) - - async def test_add_side_effect_normal_function(self): - def addition(var): - return var + 1 - mock = AsyncMock(side_effect=addition) - result = await mock(5) - self.assertEqual(result, 6) - - async def test_add_side_effect_iterable(self): - vals = [1, 2, 3] - mock = AsyncMock(side_effect=vals) - for item in vals: - self.assertEqual(await mock(), item) - - with self.assertRaises(StopAsyncIteration) as e: - await mock() - - async def test_add_side_effect_exception_iterable(self): - class SampleException(Exception): - pass - - vals = [1, SampleException("foo")] - mock = AsyncMock(side_effect=vals) - self.assertEqual(await mock(), 1) - - with self.assertRaises(SampleException) as e: - await mock() - - async def test_return_value_AsyncMock(self): - value = AsyncMock(return_value=10) - mock = AsyncMock(return_value=value) - result = await mock() - self.assertIs(result, value) - - async def test_return_value_awaitable(self): - fut = asyncio.Future() - fut.set_result(None) - mock = AsyncMock(return_value=fut) - result = await mock() - self.assertIsInstance(result, asyncio.Future) - - async def test_side_effect_awaitable_values(self): - fut = asyncio.Future() - fut.set_result(None) - - mock = AsyncMock(side_effect=[fut]) - result = await mock() - self.assertIsInstance(result, asyncio.Future) - - with self.assertRaises(StopAsyncIteration): - await mock() - - async def test_side_effect_is_AsyncMock(self): - effect = AsyncMock(return_value=10) - mock = AsyncMock(side_effect=effect) - - result = await mock() - self.assertEqual(result, 10) - - async def test_wraps_coroutine(self): - value = asyncio.Future() - - ran = False - async def inner(): - nonlocal ran - ran = True - return value - - mock = AsyncMock(wraps=inner) - result = await mock() - self.assertEqual(result, value) - mock.assert_awaited() - self.assertTrue(ran) - - async def test_wraps_normal_function(self): - value = 1 - - ran = False - def inner(): - nonlocal ran - ran = True - return value - - mock = AsyncMock(wraps=inner) - result = await mock() - self.assertEqual(result, value) - mock.assert_awaited() - self.assertTrue(ran) - - async def test_await_args_list_order(self): - async_mock = AsyncMock() - mock2 = async_mock(2) - mock1 = async_mock(1) - await mock1 - await mock2 - async_mock.assert_has_awaits([call(1), call(2)]) - self.assertEqual(async_mock.await_args_list, [call(1), call(2)]) - self.assertEqual(async_mock.call_args_list, [call(2), call(1)]) - - -class AsyncMagicMethods(unittest.TestCase): - def test_async_magic_methods_return_async_mocks(self): - m_mock = MagicMock() - self.assertIsInstance(m_mock.__aenter__, AsyncMock) - self.assertIsInstance(m_mock.__aexit__, AsyncMock) - self.assertIsInstance(m_mock.__anext__, AsyncMock) - # __aiter__ is actually a synchronous object - # so should return a MagicMock - self.assertIsInstance(m_mock.__aiter__, MagicMock) - - def test_sync_magic_methods_return_magic_mocks(self): - a_mock = AsyncMock() - self.assertIsInstance(a_mock.__enter__, MagicMock) - self.assertIsInstance(a_mock.__exit__, MagicMock) - self.assertIsInstance(a_mock.__next__, MagicMock) - self.assertIsInstance(a_mock.__len__, MagicMock) - - def test_magicmock_has_async_magic_methods(self): - m_mock = MagicMock() - self.assertTrue(hasattr(m_mock, "__aenter__")) - self.assertTrue(hasattr(m_mock, "__aexit__")) - self.assertTrue(hasattr(m_mock, "__anext__")) - - def test_asyncmock_has_sync_magic_methods(self): - a_mock = AsyncMock() - self.assertTrue(hasattr(a_mock, "__enter__")) - self.assertTrue(hasattr(a_mock, "__exit__")) - self.assertTrue(hasattr(a_mock, "__next__")) - self.assertTrue(hasattr(a_mock, "__len__")) - - def test_magic_methods_are_async_functions(self): - m_mock = MagicMock() - self.assertIsInstance(m_mock.__aenter__, AsyncMock) - self.assertIsInstance(m_mock.__aexit__, AsyncMock) - # AsyncMocks are also coroutine functions - self.assertTrue(iscoroutinefunction(m_mock.__aenter__)) - self.assertTrue(iscoroutinefunction(m_mock.__aexit__)) - -class AsyncContextManagerTest(unittest.TestCase): - - class WithAsyncContextManager: - async def __aenter__(self, *args, **kwargs): pass - - async def __aexit__(self, *args, **kwargs): pass - - class WithSyncContextManager: - def __enter__(self, *args, **kwargs): pass - - def __exit__(self, *args, **kwargs): pass - - class ProductionCode: - # Example real-world(ish) code - def __init__(self): - self.session = None - - async def main(self): - async with self.session.post('https://python.org') as response: - val = await response.json() - return val - - def test_set_return_value_of_aenter(self): - def inner_test(mock_type): - pc = self.ProductionCode() - pc.session = MagicMock(name='sessionmock') - cm = mock_type(name='magic_cm') - response = AsyncMock(name='response') - response.json = AsyncMock(return_value={'json': 123}) - cm.__aenter__.return_value = response - pc.session.post.return_value = cm - result = run(pc.main()) - self.assertEqual(result, {'json': 123}) - - for mock_type in [AsyncMock, MagicMock]: - with self.subTest(f"test set return value of aenter with {mock_type}"): - inner_test(mock_type) - - def test_mock_supports_async_context_manager(self): - def inner_test(mock_type): - called = False - cm = self.WithAsyncContextManager() - cm_mock = mock_type(cm) - - async def use_context_manager(): - nonlocal called - async with cm_mock as result: - called = True - return result - - cm_result = run(use_context_manager()) - self.assertTrue(called) - self.assertTrue(cm_mock.__aenter__.called) - self.assertTrue(cm_mock.__aexit__.called) - cm_mock.__aenter__.assert_awaited() - cm_mock.__aexit__.assert_awaited() - # We mock __aenter__ so it does not return self - self.assertIsNot(cm_mock, cm_result) - - for mock_type in [AsyncMock, MagicMock]: - with self.subTest(f"test context manager magics with {mock_type}"): - inner_test(mock_type) - - - def test_mock_customize_async_context_manager(self): - instance = self.WithAsyncContextManager() - mock_instance = MagicMock(instance) - - expected_result = object() - mock_instance.__aenter__.return_value = expected_result - - async def use_context_manager(): - async with mock_instance as result: - return result - - self.assertIs(run(use_context_manager()), expected_result) - - def test_mock_customize_async_context_manager_with_coroutine(self): - enter_called = False - exit_called = False - - async def enter_coroutine(*args): - nonlocal enter_called - enter_called = True - - async def exit_coroutine(*args): - nonlocal exit_called - exit_called = True - - instance = self.WithAsyncContextManager() - mock_instance = MagicMock(instance) - - mock_instance.__aenter__ = enter_coroutine - mock_instance.__aexit__ = exit_coroutine - - async def use_context_manager(): - async with mock_instance: - pass - - run(use_context_manager()) - self.assertTrue(enter_called) - self.assertTrue(exit_called) - - def test_context_manager_raise_exception_by_default(self): - async def raise_in(context_manager): - async with context_manager: - raise TypeError() - - instance = self.WithAsyncContextManager() - mock_instance = MagicMock(instance) - with self.assertRaises(TypeError): - run(raise_in(mock_instance)) - - -class AsyncIteratorTest(unittest.TestCase): - class WithAsyncIterator(object): - def __init__(self): - self.items = ["foo", "NormalFoo", "baz"] - - def __aiter__(self): pass - - async def __anext__(self): pass - - def test_aiter_set_return_value(self): - mock_iter = AsyncMock(name="tester") - mock_iter.__aiter__.return_value = [1, 2, 3] - async def main(): - return [i async for i in mock_iter] - result = run(main()) - self.assertEqual(result, [1, 2, 3]) - - def test_mock_aiter_and_anext_asyncmock(self): - def inner_test(mock_type): - instance = self.WithAsyncIterator() - mock_instance = mock_type(instance) - # Check that the mock and the real thing bahave the same - # __aiter__ is not actually async, so not a coroutinefunction - self.assertFalse(iscoroutinefunction(instance.__aiter__)) - self.assertFalse(iscoroutinefunction(mock_instance.__aiter__)) - # __anext__ is async - self.assertTrue(iscoroutinefunction(instance.__anext__)) - self.assertTrue(iscoroutinefunction(mock_instance.__anext__)) - - for mock_type in [AsyncMock, MagicMock]: - with self.subTest(f"test aiter and anext corourtine with {mock_type}"): - inner_test(mock_type) - - - def test_mock_async_for(self): - async def iterate(iterator): - accumulator = [] - async for item in iterator: - accumulator.append(item) - - return accumulator - - expected = ["FOO", "BAR", "BAZ"] - def test_default(mock_type): - mock_instance = mock_type(self.WithAsyncIterator()) - self.assertEqual(run(iterate(mock_instance)), []) - - - def test_set_return_value(mock_type): - mock_instance = mock_type(self.WithAsyncIterator()) - mock_instance.__aiter__.return_value = expected[:] - self.assertEqual(run(iterate(mock_instance)), expected) - - def test_set_return_value_iter(mock_type): - mock_instance = mock_type(self.WithAsyncIterator()) - mock_instance.__aiter__.return_value = iter(expected[:]) - self.assertEqual(run(iterate(mock_instance)), expected) - - for mock_type in [AsyncMock, MagicMock]: - with self.subTest(f"default value with {mock_type}"): - test_default(mock_type) - - with self.subTest(f"set return_value with {mock_type}"): - test_set_return_value(mock_type) - - with self.subTest(f"set return_value iterator with {mock_type}"): - test_set_return_value_iter(mock_type) - - -class AsyncMockAssert(unittest.TestCase): - def setUp(self): - self.mock = AsyncMock() - - async def _runnable_test(self, *args, **kwargs): - await self.mock(*args, **kwargs) - - async def _await_coroutine(self, coroutine): - return await coroutine - - def test_assert_called_but_not_awaited(self): - mock = AsyncMock(AsyncClass) - with assertNeverAwaited(self): - mock.async_method() - self.assertTrue(iscoroutinefunction(mock.async_method)) - mock.async_method.assert_called() - mock.async_method.assert_called_once() - mock.async_method.assert_called_once_with() - with self.assertRaises(AssertionError): - mock.assert_awaited() - with self.assertRaises(AssertionError): - mock.async_method.assert_awaited() - - def test_assert_called_then_awaited(self): - mock = AsyncMock(AsyncClass) - mock_coroutine = mock.async_method() - mock.async_method.assert_called() - mock.async_method.assert_called_once() - mock.async_method.assert_called_once_with() - with self.assertRaises(AssertionError): - mock.async_method.assert_awaited() - - run(self._await_coroutine(mock_coroutine)) - # Assert we haven't re-called the function - mock.async_method.assert_called_once() - mock.async_method.assert_awaited() - mock.async_method.assert_awaited_once() - mock.async_method.assert_awaited_once_with() - - def test_assert_called_and_awaited_at_same_time(self): - with self.assertRaises(AssertionError): - self.mock.assert_awaited() - - with self.assertRaises(AssertionError): - self.mock.assert_called() - - run(self._runnable_test()) - self.mock.assert_called_once() - self.mock.assert_awaited_once() - - def test_assert_called_twice_and_awaited_once(self): - mock = AsyncMock(AsyncClass) - coroutine = mock.async_method() - # The first call will be awaited so no warning there - # But this call will never get awaited, so it will warn here - with assertNeverAwaited(self): - mock.async_method() - with self.assertRaises(AssertionError): - mock.async_method.assert_awaited() - mock.async_method.assert_called() - run(self._await_coroutine(coroutine)) - mock.async_method.assert_awaited() - mock.async_method.assert_awaited_once() - - def test_assert_called_once_and_awaited_twice(self): - mock = AsyncMock(AsyncClass) - coroutine = mock.async_method() - mock.async_method.assert_called_once() - run(self._await_coroutine(coroutine)) - with self.assertRaises(RuntimeError): - # Cannot reuse already awaited coroutine - run(self._await_coroutine(coroutine)) - mock.async_method.assert_awaited() - - def test_assert_awaited_but_not_called(self): - with self.assertRaises(AssertionError): - self.mock.assert_awaited() - with self.assertRaises(AssertionError): - self.mock.assert_called() - with self.assertRaises(TypeError): - # You cannot await an AsyncMock, it must be a coroutine - run(self._await_coroutine(self.mock)) - - with self.assertRaises(AssertionError): - self.mock.assert_awaited() - with self.assertRaises(AssertionError): - self.mock.assert_called() - - def test_assert_has_calls_not_awaits(self): - kalls = [call('foo')] - with assertNeverAwaited(self): - self.mock('foo') - self.mock.assert_has_calls(kalls) - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(kalls) - - def test_assert_has_mock_calls_on_async_mock_no_spec(self): - with assertNeverAwaited(self): - self.mock() - kalls_empty = [('', (), {})] - self.assertEqual(self.mock.mock_calls, kalls_empty) - - with assertNeverAwaited(self): - self.mock('foo') - with assertNeverAwaited(self): - self.mock('baz') - mock_kalls = ([call(), call('foo'), call('baz')]) - self.assertEqual(self.mock.mock_calls, mock_kalls) - - def test_assert_has_mock_calls_on_async_mock_with_spec(self): - a_class_mock = AsyncMock(AsyncClass) - with assertNeverAwaited(self): - a_class_mock.async_method() - kalls_empty = [('', (), {})] - self.assertEqual(a_class_mock.async_method.mock_calls, kalls_empty) - self.assertEqual(a_class_mock.mock_calls, [call.async_method()]) - - with assertNeverAwaited(self): - a_class_mock.async_method(1, 2, 3, a=4, b=5) - method_kalls = [call(), call(1, 2, 3, a=4, b=5)] - mock_kalls = [call.async_method(), call.async_method(1, 2, 3, a=4, b=5)] - self.assertEqual(a_class_mock.async_method.mock_calls, method_kalls) - self.assertEqual(a_class_mock.mock_calls, mock_kalls) - - def test_async_method_calls_recorded(self): - with assertNeverAwaited(self): - self.mock.something(3, fish=None) - with assertNeverAwaited(self): - self.mock.something_else.something(6, cake=sentinel.Cake) - - self.assertEqual(self.mock.method_calls, [ - ("something", (3,), {'fish': None}), - ("something_else.something", (6,), {'cake': sentinel.Cake}) - ], - "method calls not recorded correctly") - self.assertEqual(self.mock.something_else.method_calls, - [("something", (6,), {'cake': sentinel.Cake})], - "method calls not recorded correctly") - - def test_async_arg_lists(self): - def assert_attrs(mock): - names = ('call_args_list', 'method_calls', 'mock_calls') - for name in names: - attr = getattr(mock, name) - self.assertIsInstance(attr, _CallList) - self.assertIsInstance(attr, list) - self.assertEqual(attr, []) - - assert_attrs(self.mock) - with assertNeverAwaited(self): - self.mock() - with assertNeverAwaited(self): - self.mock(1, 2) - with assertNeverAwaited(self): - self.mock(a=3) - - self.mock.reset_mock() - assert_attrs(self.mock) - - a_mock = AsyncMock(AsyncClass) - with assertNeverAwaited(self): - a_mock.async_method() - with assertNeverAwaited(self): - a_mock.async_method(1, a=3) - - a_mock.reset_mock() - assert_attrs(a_mock) - - def test_assert_awaited(self): - with self.assertRaises(AssertionError): - self.mock.assert_awaited() - - run(self._runnable_test()) - self.mock.assert_awaited() - - def test_assert_awaited_once(self): - with self.assertRaises(AssertionError): - self.mock.assert_awaited_once() - - run(self._runnable_test()) - self.mock.assert_awaited_once() - - run(self._runnable_test()) - with self.assertRaises(AssertionError): - self.mock.assert_awaited_once() - - def test_assert_awaited_with(self): - msg = 'Not awaited' - with self.assertRaisesRegex(AssertionError, msg): - self.mock.assert_awaited_with('foo') - - run(self._runnable_test()) - msg = 'expected await not found' - with self.assertRaisesRegex(AssertionError, msg): - self.mock.assert_awaited_with('foo') - - run(self._runnable_test('foo')) - self.mock.assert_awaited_with('foo') - - run(self._runnable_test('SomethingElse')) - with self.assertRaises(AssertionError): - self.mock.assert_awaited_with('foo') - - def test_assert_awaited_once_with(self): - with self.assertRaises(AssertionError): - self.mock.assert_awaited_once_with('foo') - - run(self._runnable_test('foo')) - self.mock.assert_awaited_once_with('foo') - - run(self._runnable_test('foo')) - with self.assertRaises(AssertionError): - self.mock.assert_awaited_once_with('foo') - - def test_assert_any_wait(self): - with self.assertRaises(AssertionError): - self.mock.assert_any_await('foo') - - run(self._runnable_test('baz')) - with self.assertRaises(AssertionError): - self.mock.assert_any_await('foo') - - run(self._runnable_test('foo')) - self.mock.assert_any_await('foo') - - run(self._runnable_test('SomethingElse')) - self.mock.assert_any_await('foo') - - def test_assert_has_awaits_no_order(self): - calls = [call('foo'), call('baz')] - - with self.assertRaises(AssertionError) as cm: - self.mock.assert_has_awaits(calls) - self.assertEqual(len(cm.exception.args), 1) - - run(self._runnable_test('foo')) - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(calls) - - run(self._runnable_test('foo')) - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(calls) - - run(self._runnable_test('baz')) - self.mock.assert_has_awaits(calls) - - run(self._runnable_test('SomethingElse')) - self.mock.assert_has_awaits(calls) - - def test_awaits_asserts_with_any(self): - class Foo: - def __eq__(self, other): pass - - run(self._runnable_test(Foo(), 1)) - - self.mock.assert_has_awaits([call(ANY, 1)]) - self.mock.assert_awaited_with(ANY, 1) - self.mock.assert_any_await(ANY, 1) - - def test_awaits_asserts_with_spec_and_any(self): - class Foo: - def __eq__(self, other): pass - - mock_with_spec = AsyncMock(spec=Foo) - - async def _custom_mock_runnable_test(*args): - await mock_with_spec(*args) - - run(_custom_mock_runnable_test(Foo(), 1)) - mock_with_spec.assert_has_awaits([call(ANY, 1)]) - mock_with_spec.assert_awaited_with(ANY, 1) - mock_with_spec.assert_any_await(ANY, 1) - - def test_assert_has_awaits_ordered(self): - calls = [call('foo'), call('baz')] - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(calls, any_order=True) - - run(self._runnable_test('baz')) - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(calls, any_order=True) - - run(self._runnable_test('bamf')) - with self.assertRaises(AssertionError): - self.mock.assert_has_awaits(calls, any_order=True) - - run(self._runnable_test('foo')) - self.mock.assert_has_awaits(calls, any_order=True) - - run(self._runnable_test('qux')) - self.mock.assert_has_awaits(calls, any_order=True) - - def test_assert_not_awaited(self): - self.mock.assert_not_awaited() - - run(self._runnable_test()) - with self.assertRaises(AssertionError): - self.mock.assert_not_awaited() - - def test_assert_has_awaits_not_matching_spec_error(self): - async def f(x=None): pass - - self.mock = AsyncMock(spec=f) - run(self._runnable_test(1)) - - with self.assertRaisesRegex( - AssertionError, - '^{}$'.format( - re.escape('Awaits not found.\n' - 'Expected: [call()]\n' - 'Actual: [call(1)]'))) as cm: - self.mock.assert_has_awaits([call()]) - self.assertIsNone(cm.exception.__cause__) - - with self.assertRaisesRegex( - AssertionError, - '^{}$'.format( - re.escape( - 'Error processing expected awaits.\n' - "Errors: [None, TypeError('too many positional " - "arguments')]\n" - 'Expected: [call(), call(1, 2)]\n' - 'Actual: [call(1)]'))) as cm: - self.mock.assert_has_awaits([call(), call(1, 2)]) - self.assertIsInstance(cm.exception.__cause__, TypeError) diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py b/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py deleted file mode 100644 index 5eadc00704..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testcallable.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -import unittest -from unittest.test.testmock.support import is_instance, X, SomeClass - -from unittest.mock import ( - Mock, MagicMock, NonCallableMagicMock, - NonCallableMock, patch, create_autospec, - CallableMixin -) - - - -class TestCallable(unittest.TestCase): - - def assertNotCallable(self, mock): - self.assertTrue(is_instance(mock, NonCallableMagicMock)) - self.assertFalse(is_instance(mock, CallableMixin)) - - - def test_non_callable(self): - for mock in NonCallableMagicMock(), NonCallableMock(): - self.assertRaises(TypeError, mock) - self.assertFalse(hasattr(mock, '__call__')) - self.assertIn(mock.__class__.__name__, repr(mock)) - - - def test_hierarchy(self): - self.assertTrue(issubclass(MagicMock, Mock)) - self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) - - - def test_attributes(self): - one = NonCallableMock() - self.assertTrue(issubclass(type(one.one), Mock)) - - two = NonCallableMagicMock() - self.assertTrue(issubclass(type(two.two), MagicMock)) - - - def test_subclasses(self): - class MockSub(Mock): - pass - - one = MockSub() - self.assertTrue(issubclass(type(one.one), MockSub)) - - class MagicSub(MagicMock): - pass - - two = MagicSub() - self.assertTrue(issubclass(type(two.two), MagicSub)) - - - def test_patch_spec(self): - patcher = patch('%s.X' % __name__, spec=True) - mock = patcher.start() - self.addCleanup(patcher.stop) - - instance = mock() - mock.assert_called_once_with() - - self.assertNotCallable(instance) - self.assertRaises(TypeError, instance) - - - def test_patch_spec_set(self): - patcher = patch('%s.X' % __name__, spec_set=True) - mock = patcher.start() - self.addCleanup(patcher.stop) - - instance = mock() - mock.assert_called_once_with() - - self.assertNotCallable(instance) - self.assertRaises(TypeError, instance) - - - def test_patch_spec_instance(self): - patcher = patch('%s.X' % __name__, spec=X()) - mock = patcher.start() - self.addCleanup(patcher.stop) - - self.assertNotCallable(mock) - self.assertRaises(TypeError, mock) - - - def test_patch_spec_set_instance(self): - patcher = patch('%s.X' % __name__, spec_set=X()) - mock = patcher.start() - self.addCleanup(patcher.stop) - - self.assertNotCallable(mock) - self.assertRaises(TypeError, mock) - - - def test_patch_spec_callable_class(self): - class CallableX(X): - def __call__(self): pass - - class Sub(CallableX): - pass - - class Multi(SomeClass, Sub): - pass - - for arg in 'spec', 'spec_set': - for Klass in CallableX, Sub, Multi: - with patch('%s.X' % __name__, **{arg: Klass}) as mock: - instance = mock() - mock.assert_called_once_with() - - self.assertTrue(is_instance(instance, MagicMock)) - # inherited spec - self.assertRaises(AttributeError, getattr, instance, - 'foobarbaz') - - result = instance() - # instance is callable, result has no spec - instance.assert_called_once_with() - - result(3, 2, 1) - result.assert_called_once_with(3, 2, 1) - result.foo(3, 2, 1) - result.foo.assert_called_once_with(3, 2, 1) - - - def test_create_autospec(self): - mock = create_autospec(X) - instance = mock() - self.assertRaises(TypeError, instance) - - mock = create_autospec(X()) - self.assertRaises(TypeError, mock) - - - def test_create_autospec_instance(self): - mock = create_autospec(SomeClass, instance=True) - - self.assertRaises(TypeError, mock) - mock.wibble() - mock.wibble.assert_called_once_with() - - self.assertRaises(TypeError, mock.wibble, 'some', 'args') - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py b/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py deleted file mode 100644 index 9e7ec5d62d..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testhelpers.py +++ /dev/null @@ -1,1127 +0,0 @@ -import inspect -import time -import types -import unittest - -from unittest.mock import ( - call, _Call, create_autospec, MagicMock, - Mock, ANY, _CallList, patch, PropertyMock, _callable -) - -from datetime import datetime -from functools import partial - -class SomeClass(object): - def one(self, a, b): pass - def two(self): pass - def three(self, a=None): pass - - - -class AnyTest(unittest.TestCase): - - def test_any(self): - self.assertEqual(ANY, object()) - - mock = Mock() - mock(ANY) - mock.assert_called_with(ANY) - - mock = Mock() - mock(foo=ANY) - mock.assert_called_with(foo=ANY) - - def test_repr(self): - self.assertEqual(repr(ANY), '') - self.assertEqual(str(ANY), '') - - - def test_any_and_datetime(self): - mock = Mock() - mock(datetime.now(), foo=datetime.now()) - - mock.assert_called_with(ANY, foo=ANY) - - - def test_any_mock_calls_comparison_order(self): - mock = Mock() - class Foo(object): - def __eq__(self, other): pass - def __ne__(self, other): pass - - for d in datetime.now(), Foo(): - mock.reset_mock() - - mock(d, foo=d, bar=d) - mock.method(d, zinga=d, alpha=d) - mock().method(a1=d, z99=d) - - expected = [ - call(ANY, foo=ANY, bar=ANY), - call.method(ANY, zinga=ANY, alpha=ANY), - call(), call().method(a1=ANY, z99=ANY) - ] - self.assertEqual(expected, mock.mock_calls) - self.assertEqual(mock.mock_calls, expected) - - def test_any_no_spec(self): - # This is a regression test for bpo-37555 - class Foo: - def __eq__(self, other): pass - - mock = Mock() - mock(Foo(), 1) - mock.assert_has_calls([call(ANY, 1)]) - mock.assert_called_with(ANY, 1) - mock.assert_any_call(ANY, 1) - - def test_any_and_spec_set(self): - # This is a regression test for bpo-37555 - class Foo: - def __eq__(self, other): pass - - mock = Mock(spec=Foo) - - mock(Foo(), 1) - mock.assert_has_calls([call(ANY, 1)]) - mock.assert_called_with(ANY, 1) - mock.assert_any_call(ANY, 1) - -class CallTest(unittest.TestCase): - - def test_call_with_call(self): - kall = _Call() - self.assertEqual(kall, _Call()) - self.assertEqual(kall, _Call(('',))) - self.assertEqual(kall, _Call(((),))) - self.assertEqual(kall, _Call(({},))) - self.assertEqual(kall, _Call(('', ()))) - self.assertEqual(kall, _Call(('', {}))) - self.assertEqual(kall, _Call(('', (), {}))) - self.assertEqual(kall, _Call(('foo',))) - self.assertEqual(kall, _Call(('bar', ()))) - self.assertEqual(kall, _Call(('baz', {}))) - self.assertEqual(kall, _Call(('spam', (), {}))) - - kall = _Call(((1, 2, 3),)) - self.assertEqual(kall, _Call(((1, 2, 3),))) - self.assertEqual(kall, _Call(('', (1, 2, 3)))) - self.assertEqual(kall, _Call(((1, 2, 3), {}))) - self.assertEqual(kall, _Call(('', (1, 2, 3), {}))) - - kall = _Call(((1, 2, 4),)) - self.assertNotEqual(kall, _Call(('', (1, 2, 3)))) - self.assertNotEqual(kall, _Call(('', (1, 2, 3), {}))) - - kall = _Call(('foo', (1, 2, 4),)) - self.assertNotEqual(kall, _Call(('', (1, 2, 4)))) - self.assertNotEqual(kall, _Call(('', (1, 2, 4), {}))) - self.assertNotEqual(kall, _Call(('bar', (1, 2, 4)))) - self.assertNotEqual(kall, _Call(('bar', (1, 2, 4), {}))) - - kall = _Call(({'a': 3},)) - self.assertEqual(kall, _Call(('', (), {'a': 3}))) - self.assertEqual(kall, _Call(('', {'a': 3}))) - self.assertEqual(kall, _Call(((), {'a': 3}))) - self.assertEqual(kall, _Call(({'a': 3},))) - - - def test_empty__Call(self): - args = _Call() - - self.assertEqual(args, ()) - self.assertEqual(args, ('foo',)) - self.assertEqual(args, ((),)) - self.assertEqual(args, ('foo', ())) - self.assertEqual(args, ('foo',(), {})) - self.assertEqual(args, ('foo', {})) - self.assertEqual(args, ({},)) - - - def test_named_empty_call(self): - args = _Call(('foo', (), {})) - - self.assertEqual(args, ('foo',)) - self.assertEqual(args, ('foo', ())) - self.assertEqual(args, ('foo',(), {})) - self.assertEqual(args, ('foo', {})) - - self.assertNotEqual(args, ((),)) - self.assertNotEqual(args, ()) - self.assertNotEqual(args, ({},)) - self.assertNotEqual(args, ('bar',)) - self.assertNotEqual(args, ('bar', ())) - self.assertNotEqual(args, ('bar', {})) - - - def test_call_with_args(self): - args = _Call(((1, 2, 3), {})) - - self.assertEqual(args, ((1, 2, 3),)) - self.assertEqual(args, ('foo', (1, 2, 3))) - self.assertEqual(args, ('foo', (1, 2, 3), {})) - self.assertEqual(args, ((1, 2, 3), {})) - self.assertEqual(args.args, (1, 2, 3)) - self.assertEqual(args.kwargs, {}) - - - def test_named_call_with_args(self): - args = _Call(('foo', (1, 2, 3), {})) - - self.assertEqual(args, ('foo', (1, 2, 3))) - self.assertEqual(args, ('foo', (1, 2, 3), {})) - self.assertEqual(args.args, (1, 2, 3)) - self.assertEqual(args.kwargs, {}) - - self.assertNotEqual(args, ((1, 2, 3),)) - self.assertNotEqual(args, ((1, 2, 3), {})) - - - def test_call_with_kwargs(self): - args = _Call(((), dict(a=3, b=4))) - - self.assertEqual(args, (dict(a=3, b=4),)) - self.assertEqual(args, ('foo', dict(a=3, b=4))) - self.assertEqual(args, ('foo', (), dict(a=3, b=4))) - self.assertEqual(args, ((), dict(a=3, b=4))) - self.assertEqual(args.args, ()) - self.assertEqual(args.kwargs, dict(a=3, b=4)) - - - def test_named_call_with_kwargs(self): - args = _Call(('foo', (), dict(a=3, b=4))) - - self.assertEqual(args, ('foo', dict(a=3, b=4))) - self.assertEqual(args, ('foo', (), dict(a=3, b=4))) - self.assertEqual(args.args, ()) - self.assertEqual(args.kwargs, dict(a=3, b=4)) - - self.assertNotEqual(args, (dict(a=3, b=4),)) - self.assertNotEqual(args, ((), dict(a=3, b=4))) - - - def test_call_with_args_call_empty_name(self): - args = _Call(((1, 2, 3), {})) - - self.assertEqual(args, call(1, 2, 3)) - self.assertEqual(call(1, 2, 3), args) - self.assertIn(call(1, 2, 3), [args]) - - - def test_call_ne(self): - self.assertNotEqual(_Call(((1, 2, 3),)), call(1, 2)) - self.assertFalse(_Call(((1, 2, 3),)) != call(1, 2, 3)) - self.assertTrue(_Call(((1, 2), {})) != call(1, 2, 3)) - - - def test_call_non_tuples(self): - kall = _Call(((1, 2, 3),)) - for value in 1, None, self, int: - self.assertNotEqual(kall, value) - self.assertFalse(kall == value) - - - def test_repr(self): - self.assertEqual(repr(_Call()), 'call()') - self.assertEqual(repr(_Call(('foo',))), 'call.foo()') - - self.assertEqual(repr(_Call(((1, 2, 3), {'a': 'b'}))), - "call(1, 2, 3, a='b')") - self.assertEqual(repr(_Call(('bar', (1, 2, 3), {'a': 'b'}))), - "call.bar(1, 2, 3, a='b')") - - self.assertEqual(repr(call), 'call') - self.assertEqual(str(call), 'call') - - self.assertEqual(repr(call()), 'call()') - self.assertEqual(repr(call(1)), 'call(1)') - self.assertEqual(repr(call(zz='thing')), "call(zz='thing')") - - self.assertEqual(repr(call().foo), 'call().foo') - self.assertEqual(repr(call(1).foo.bar(a=3).bing), - 'call().foo.bar().bing') - self.assertEqual( - repr(call().foo(1, 2, a=3)), - "call().foo(1, 2, a=3)" - ) - self.assertEqual(repr(call()()), "call()()") - self.assertEqual(repr(call(1)(2)), "call()(2)") - self.assertEqual( - repr(call()().bar().baz.beep(1)), - "call()().bar().baz.beep(1)" - ) - - - def test_call(self): - self.assertEqual(call(), ('', (), {})) - self.assertEqual(call('foo', 'bar', one=3, two=4), - ('', ('foo', 'bar'), {'one': 3, 'two': 4})) - - mock = Mock() - mock(1, 2, 3) - mock(a=3, b=6) - self.assertEqual(mock.call_args_list, - [call(1, 2, 3), call(a=3, b=6)]) - - def test_attribute_call(self): - self.assertEqual(call.foo(1), ('foo', (1,), {})) - self.assertEqual(call.bar.baz(fish='eggs'), - ('bar.baz', (), {'fish': 'eggs'})) - - mock = Mock() - mock.foo(1, 2 ,3) - mock.bar.baz(a=3, b=6) - self.assertEqual(mock.method_calls, - [call.foo(1, 2, 3), call.bar.baz(a=3, b=6)]) - - - def test_extended_call(self): - result = call(1).foo(2).bar(3, a=4) - self.assertEqual(result, ('().foo().bar', (3,), dict(a=4))) - - mock = MagicMock() - mock(1, 2, a=3, b=4) - self.assertEqual(mock.call_args, call(1, 2, a=3, b=4)) - self.assertNotEqual(mock.call_args, call(1, 2, 3)) - - self.assertEqual(mock.call_args_list, [call(1, 2, a=3, b=4)]) - self.assertEqual(mock.mock_calls, [call(1, 2, a=3, b=4)]) - - mock = MagicMock() - mock.foo(1).bar()().baz.beep(a=6) - - last_call = call.foo(1).bar()().baz.beep(a=6) - self.assertEqual(mock.mock_calls[-1], last_call) - self.assertEqual(mock.mock_calls, last_call.call_list()) - - - def test_extended_not_equal(self): - a = call(x=1).foo - b = call(x=2).foo - self.assertEqual(a, a) - self.assertEqual(b, b) - self.assertNotEqual(a, b) - - - def test_nested_calls_not_equal(self): - a = call(x=1).foo().bar - b = call(x=2).foo().bar - self.assertEqual(a, a) - self.assertEqual(b, b) - self.assertNotEqual(a, b) - - - def test_call_list(self): - mock = MagicMock() - mock(1) - self.assertEqual(call(1).call_list(), mock.mock_calls) - - mock = MagicMock() - mock(1).method(2) - self.assertEqual(call(1).method(2).call_list(), - mock.mock_calls) - - mock = MagicMock() - mock(1).method(2)(3) - self.assertEqual(call(1).method(2)(3).call_list(), - mock.mock_calls) - - mock = MagicMock() - int(mock(1).method(2)(3).foo.bar.baz(4)(5)) - kall = call(1).method(2)(3).foo.bar.baz(4)(5).__int__() - self.assertEqual(kall.call_list(), mock.mock_calls) - - - def test_call_any(self): - self.assertEqual(call, ANY) - - m = MagicMock() - int(m) - self.assertEqual(m.mock_calls, [ANY]) - self.assertEqual([ANY], m.mock_calls) - - - def test_two_args_call(self): - args = _Call(((1, 2), {'a': 3}), two=True) - self.assertEqual(len(args), 2) - self.assertEqual(args[0], (1, 2)) - self.assertEqual(args[1], {'a': 3}) - - other_args = _Call(((1, 2), {'a': 3})) - self.assertEqual(args, other_args) - - def test_call_with_name(self): - self.assertEqual(_Call((), 'foo')[0], 'foo') - self.assertEqual(_Call((('bar', 'barz'),),)[0], '') - self.assertEqual(_Call((('bar', 'barz'), {'hello': 'world'}),)[0], '') - - def test_dunder_call(self): - m = MagicMock() - m().foo()['bar']() - self.assertEqual( - m.mock_calls, - [call(), call().foo(), call().foo().__getitem__('bar'), call().foo().__getitem__()()] - ) - m = MagicMock() - m().foo()['bar'] = 1 - self.assertEqual( - m.mock_calls, - [call(), call().foo(), call().foo().__setitem__('bar', 1)] - ) - m = MagicMock() - iter(m().foo()) - self.assertEqual( - m.mock_calls, - [call(), call().foo(), call().foo().__iter__()] - ) - - -class SpecSignatureTest(unittest.TestCase): - - def _check_someclass_mock(self, mock): - self.assertRaises(AttributeError, getattr, mock, 'foo') - mock.one(1, 2) - mock.one.assert_called_with(1, 2) - self.assertRaises(AssertionError, - mock.one.assert_called_with, 3, 4) - self.assertRaises(TypeError, mock.one, 1) - - mock.two() - mock.two.assert_called_with() - self.assertRaises(AssertionError, - mock.two.assert_called_with, 3) - self.assertRaises(TypeError, mock.two, 1) - - mock.three() - mock.three.assert_called_with() - self.assertRaises(AssertionError, - mock.three.assert_called_with, 3) - self.assertRaises(TypeError, mock.three, 3, 2) - - mock.three(1) - mock.three.assert_called_with(1) - - mock.three(a=1) - mock.three.assert_called_with(a=1) - - - def test_basic(self): - mock = create_autospec(SomeClass) - self._check_someclass_mock(mock) - mock = create_autospec(SomeClass()) - self._check_someclass_mock(mock) - - - def test_create_autospec_return_value(self): - def f(): pass - mock = create_autospec(f, return_value='foo') - self.assertEqual(mock(), 'foo') - - class Foo(object): - pass - - mock = create_autospec(Foo, return_value='foo') - self.assertEqual(mock(), 'foo') - - - def test_autospec_reset_mock(self): - m = create_autospec(int) - int(m) - m.reset_mock() - self.assertEqual(m.__int__.call_count, 0) - - - def test_mocking_unbound_methods(self): - class Foo(object): - def foo(self, foo): pass - p = patch.object(Foo, 'foo') - mock_foo = p.start() - Foo().foo(1) - - mock_foo.assert_called_with(1) - - - def test_create_autospec_keyword_arguments(self): - class Foo(object): - a = 3 - m = create_autospec(Foo, a='3') - self.assertEqual(m.a, '3') - - - def test_create_autospec_keyword_only_arguments(self): - def foo(a, *, b=None): pass - - m = create_autospec(foo) - m(1) - m.assert_called_with(1) - self.assertRaises(TypeError, m, 1, 2) - - m(2, b=3) - m.assert_called_with(2, b=3) - - - def test_function_as_instance_attribute(self): - obj = SomeClass() - def f(a): pass - obj.f = f - - mock = create_autospec(obj) - mock.f('bing') - mock.f.assert_called_with('bing') - - - def test_spec_as_list(self): - # because spec as a list of strings in the mock constructor means - # something very different we treat a list instance as the type. - mock = create_autospec([]) - mock.append('foo') - mock.append.assert_called_with('foo') - - self.assertRaises(AttributeError, getattr, mock, 'foo') - - class Foo(object): - foo = [] - - mock = create_autospec(Foo) - mock.foo.append(3) - mock.foo.append.assert_called_with(3) - self.assertRaises(AttributeError, getattr, mock.foo, 'foo') - - - def test_attributes(self): - class Sub(SomeClass): - attr = SomeClass() - - sub_mock = create_autospec(Sub) - - for mock in (sub_mock, sub_mock.attr): - self._check_someclass_mock(mock) - - - def test_spec_has_descriptor_returning_function(self): - - class CrazyDescriptor(object): - - def __get__(self, obj, type_): - if obj is None: - return lambda x: None - - class MyClass(object): - - some_attr = CrazyDescriptor() - - mock = create_autospec(MyClass) - mock.some_attr(1) - with self.assertRaises(TypeError): - mock.some_attr() - with self.assertRaises(TypeError): - mock.some_attr(1, 2) - - - def test_spec_has_function_not_in_bases(self): - - class CrazyClass(object): - - def __dir__(self): - return super(CrazyClass, self).__dir__()+['crazy'] - - def __getattr__(self, item): - if item == 'crazy': - return lambda x: x - raise AttributeError(item) - - inst = CrazyClass() - with self.assertRaises(AttributeError): - inst.other - self.assertEqual(inst.crazy(42), 42) - - mock = create_autospec(inst) - mock.crazy(42) - with self.assertRaises(TypeError): - mock.crazy() - with self.assertRaises(TypeError): - mock.crazy(1, 2) - - - def test_builtin_functions_types(self): - # we could replace builtin functions / methods with a function - # with *args / **kwargs signature. Using the builtin method type - # as a spec seems to work fairly well though. - class BuiltinSubclass(list): - def bar(self, arg): pass - sorted = sorted - attr = {} - - mock = create_autospec(BuiltinSubclass) - mock.append(3) - mock.append.assert_called_with(3) - self.assertRaises(AttributeError, getattr, mock.append, 'foo') - - mock.bar('foo') - mock.bar.assert_called_with('foo') - self.assertRaises(TypeError, mock.bar, 'foo', 'bar') - self.assertRaises(AttributeError, getattr, mock.bar, 'foo') - - mock.sorted([1, 2]) - mock.sorted.assert_called_with([1, 2]) - self.assertRaises(AttributeError, getattr, mock.sorted, 'foo') - - mock.attr.pop(3) - mock.attr.pop.assert_called_with(3) - self.assertRaises(AttributeError, getattr, mock.attr, 'foo') - - - def test_method_calls(self): - class Sub(SomeClass): - attr = SomeClass() - - mock = create_autospec(Sub) - mock.one(1, 2) - mock.two() - mock.three(3) - - expected = [call.one(1, 2), call.two(), call.three(3)] - self.assertEqual(mock.method_calls, expected) - - mock.attr.one(1, 2) - mock.attr.two() - mock.attr.three(3) - - expected.extend( - [call.attr.one(1, 2), call.attr.two(), call.attr.three(3)] - ) - self.assertEqual(mock.method_calls, expected) - - - def test_magic_methods(self): - class BuiltinSubclass(list): - attr = {} - - mock = create_autospec(BuiltinSubclass) - self.assertEqual(list(mock), []) - self.assertRaises(TypeError, int, mock) - self.assertRaises(TypeError, int, mock.attr) - self.assertEqual(list(mock), []) - - self.assertIsInstance(mock['foo'], MagicMock) - self.assertIsInstance(mock.attr['foo'], MagicMock) - - - def test_spec_set(self): - class Sub(SomeClass): - attr = SomeClass() - - for spec in (Sub, Sub()): - mock = create_autospec(spec, spec_set=True) - self._check_someclass_mock(mock) - - self.assertRaises(AttributeError, setattr, mock, 'foo', 'bar') - self.assertRaises(AttributeError, setattr, mock.attr, 'foo', 'bar') - - - def test_descriptors(self): - class Foo(object): - @classmethod - def f(cls, a, b): pass - @staticmethod - def g(a, b): pass - - class Bar(Foo): pass - - class Baz(SomeClass, Bar): pass - - for spec in (Foo, Foo(), Bar, Bar(), Baz, Baz()): - mock = create_autospec(spec) - mock.f(1, 2) - mock.f.assert_called_once_with(1, 2) - - mock.g(3, 4) - mock.g.assert_called_once_with(3, 4) - - - def test_recursive(self): - class A(object): - def a(self): pass - foo = 'foo bar baz' - bar = foo - - A.B = A - mock = create_autospec(A) - - mock() - self.assertFalse(mock.B.called) - - mock.a() - mock.B.a() - self.assertEqual(mock.method_calls, [call.a(), call.B.a()]) - - self.assertIs(A.foo, A.bar) - self.assertIsNot(mock.foo, mock.bar) - mock.foo.lower() - self.assertRaises(AssertionError, mock.bar.lower.assert_called_with) - - - def test_spec_inheritance_for_classes(self): - class Foo(object): - def a(self, x): pass - class Bar(object): - def f(self, y): pass - - class_mock = create_autospec(Foo) - - self.assertIsNot(class_mock, class_mock()) - - for this_mock in class_mock, class_mock(): - this_mock.a(x=5) - this_mock.a.assert_called_with(x=5) - this_mock.a.assert_called_with(5) - self.assertRaises(TypeError, this_mock.a, 'foo', 'bar') - self.assertRaises(AttributeError, getattr, this_mock, 'b') - - instance_mock = create_autospec(Foo()) - instance_mock.a(5) - instance_mock.a.assert_called_with(5) - instance_mock.a.assert_called_with(x=5) - self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar') - self.assertRaises(AttributeError, getattr, instance_mock, 'b') - - # The return value isn't isn't callable - self.assertRaises(TypeError, instance_mock) - - instance_mock.Bar.f(6) - instance_mock.Bar.f.assert_called_with(6) - instance_mock.Bar.f.assert_called_with(y=6) - self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g') - - instance_mock.Bar().f(6) - instance_mock.Bar().f.assert_called_with(6) - instance_mock.Bar().f.assert_called_with(y=6) - self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g') - - - def test_inherit(self): - class Foo(object): - a = 3 - - Foo.Foo = Foo - - # class - mock = create_autospec(Foo) - instance = mock() - self.assertRaises(AttributeError, getattr, instance, 'b') - - attr_instance = mock.Foo() - self.assertRaises(AttributeError, getattr, attr_instance, 'b') - - # instance - mock = create_autospec(Foo()) - self.assertRaises(AttributeError, getattr, mock, 'b') - self.assertRaises(TypeError, mock) - - # attribute instance - call_result = mock.Foo() - self.assertRaises(AttributeError, getattr, call_result, 'b') - - - def test_builtins(self): - # used to fail with infinite recursion - create_autospec(1) - - create_autospec(int) - create_autospec('foo') - create_autospec(str) - create_autospec({}) - create_autospec(dict) - create_autospec([]) - create_autospec(list) - create_autospec(set()) - create_autospec(set) - create_autospec(1.0) - create_autospec(float) - create_autospec(1j) - create_autospec(complex) - create_autospec(False) - create_autospec(True) - - - def test_function(self): - def f(a, b): pass - - mock = create_autospec(f) - self.assertRaises(TypeError, mock) - mock(1, 2) - mock.assert_called_with(1, 2) - mock.assert_called_with(1, b=2) - mock.assert_called_with(a=1, b=2) - - f.f = f - mock = create_autospec(f) - self.assertRaises(TypeError, mock.f) - mock.f(3, 4) - mock.f.assert_called_with(3, 4) - mock.f.assert_called_with(a=3, b=4) - - - def test_skip_attributeerrors(self): - class Raiser(object): - def __get__(self, obj, type=None): - if obj is None: - raise AttributeError('Can only be accessed via an instance') - - class RaiserClass(object): - raiser = Raiser() - - @staticmethod - def existing(a, b): - return a + b - - self.assertEqual(RaiserClass.existing(1, 2), 3) - s = create_autospec(RaiserClass) - self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) - self.assertEqual(s.existing(1, 2), s.existing.return_value) - self.assertRaises(AttributeError, lambda: s.nonexisting) - - # check we can fetch the raiser attribute and it has no spec - obj = s.raiser - obj.foo, obj.bar - - - def test_signature_class(self): - class Foo(object): - def __init__(self, a, b=3): pass - - mock = create_autospec(Foo) - - self.assertRaises(TypeError, mock) - mock(1) - mock.assert_called_once_with(1) - mock.assert_called_once_with(a=1) - self.assertRaises(AssertionError, mock.assert_called_once_with, 2) - - mock(4, 5) - mock.assert_called_with(4, 5) - mock.assert_called_with(a=4, b=5) - self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4) - - - def test_class_with_no_init(self): - # this used to raise an exception - # due to trying to get a signature from object.__init__ - class Foo(object): - pass - create_autospec(Foo) - - - def test_signature_callable(self): - class Callable(object): - def __init__(self, x, y): pass - def __call__(self, a): pass - - mock = create_autospec(Callable) - mock(1, 2) - mock.assert_called_once_with(1, 2) - mock.assert_called_once_with(x=1, y=2) - self.assertRaises(TypeError, mock, 'a') - - instance = mock(1, 2) - self.assertRaises(TypeError, instance) - instance(a='a') - instance.assert_called_once_with('a') - instance.assert_called_once_with(a='a') - instance('a') - instance.assert_called_with('a') - instance.assert_called_with(a='a') - - mock = create_autospec(Callable(1, 2)) - mock(a='a') - mock.assert_called_once_with(a='a') - self.assertRaises(TypeError, mock) - mock('a') - mock.assert_called_with('a') - - - def test_signature_noncallable(self): - class NonCallable(object): - def __init__(self): - pass - - mock = create_autospec(NonCallable) - instance = mock() - mock.assert_called_once_with() - self.assertRaises(TypeError, mock, 'a') - self.assertRaises(TypeError, instance) - self.assertRaises(TypeError, instance, 'a') - - mock = create_autospec(NonCallable()) - self.assertRaises(TypeError, mock) - self.assertRaises(TypeError, mock, 'a') - - - def test_create_autospec_none(self): - class Foo(object): - bar = None - - mock = create_autospec(Foo) - none = mock.bar - self.assertNotIsInstance(none, type(None)) - - none.foo() - none.foo.assert_called_once_with() - - - def test_autospec_functions_with_self_in_odd_place(self): - class Foo(object): - def f(a, self): pass - - a = create_autospec(Foo) - a.f(10) - a.f.assert_called_with(10) - a.f.assert_called_with(self=10) - a.f(self=10) - a.f.assert_called_with(10) - a.f.assert_called_with(self=10) - - - def test_autospec_data_descriptor(self): - class Descriptor(object): - def __init__(self, value): - self.value = value - - def __get__(self, obj, cls=None): - return self - - def __set__(self, obj, value): pass - - class MyProperty(property): - pass - - class Foo(object): - __slots__ = ['slot'] - - @property - def prop(self): pass - - @MyProperty - def subprop(self): pass - - desc = Descriptor(42) - - foo = create_autospec(Foo) - - def check_data_descriptor(mock_attr): - # Data descriptors don't have a spec. - self.assertIsInstance(mock_attr, MagicMock) - mock_attr(1, 2, 3) - mock_attr.abc(4, 5, 6) - mock_attr.assert_called_once_with(1, 2, 3) - mock_attr.abc.assert_called_once_with(4, 5, 6) - - # property - check_data_descriptor(foo.prop) - # property subclass - check_data_descriptor(foo.subprop) - # class __slot__ - check_data_descriptor(foo.slot) - # plain data descriptor - check_data_descriptor(foo.desc) - - - def test_autospec_on_bound_builtin_function(self): - meth = types.MethodType(time.ctime, time.time()) - self.assertIsInstance(meth(), str) - mocked = create_autospec(meth) - - # no signature, so no spec to check against - mocked() - mocked.assert_called_once_with() - mocked.reset_mock() - mocked(4, 5, 6) - mocked.assert_called_once_with(4, 5, 6) - - - def test_autospec_getattr_partial_function(self): - # bpo-32153 : getattr returning partial functions without - # __name__ should not create AttributeError in create_autospec - class Foo: - - def __getattr__(self, attribute): - return partial(lambda name: name, attribute) - - proxy = Foo() - autospec = create_autospec(proxy) - self.assertFalse(hasattr(autospec, '__name__')) - - - def test_spec_inspect_signature(self): - - def myfunc(x, y): pass - - mock = create_autospec(myfunc) - mock(1, 2) - mock(x=1, y=2) - - self.assertEqual(inspect.signature(mock), inspect.signature(myfunc)) - self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) - self.assertRaises(TypeError, mock, 1) - - - def test_spec_inspect_signature_annotations(self): - - def foo(a: int, b: int=10, *, c:int) -> int: - return a + b + c - - self.assertEqual(foo(1, 2 , c=3), 6) - mock = create_autospec(foo) - mock(1, 2, c=3) - mock(1, c=3) - - self.assertEqual(inspect.signature(mock), inspect.signature(foo)) - self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) - self.assertRaises(TypeError, mock, 1) - self.assertRaises(TypeError, mock, 1, 2, 3, c=4) - - - def test_spec_function_no_name(self): - func = lambda: 'nope' - mock = create_autospec(func) - self.assertEqual(mock.__name__, 'funcopy') - - - def test_spec_function_assert_has_calls(self): - def f(a): pass - mock = create_autospec(f) - mock(1) - mock.assert_has_calls([call(1)]) - with self.assertRaises(AssertionError): - mock.assert_has_calls([call(2)]) - - - def test_spec_function_assert_any_call(self): - def f(a): pass - mock = create_autospec(f) - mock(1) - mock.assert_any_call(1) - with self.assertRaises(AssertionError): - mock.assert_any_call(2) - - - def test_spec_function_reset_mock(self): - def f(a): pass - rv = Mock() - mock = create_autospec(f, return_value=rv) - mock(1)(2) - self.assertEqual(mock.mock_calls, [call(1)]) - self.assertEqual(rv.mock_calls, [call(2)]) - mock.reset_mock() - self.assertEqual(mock.mock_calls, []) - self.assertEqual(rv.mock_calls, []) - - -class TestCallList(unittest.TestCase): - - def test_args_list_contains_call_list(self): - mock = Mock() - self.assertIsInstance(mock.call_args_list, _CallList) - - mock(1, 2) - mock(a=3) - mock(3, 4) - mock(b=6) - - for kall in call(1, 2), call(a=3), call(3, 4), call(b=6): - self.assertIn(kall, mock.call_args_list) - - calls = [call(a=3), call(3, 4)] - self.assertIn(calls, mock.call_args_list) - calls = [call(1, 2), call(a=3)] - self.assertIn(calls, mock.call_args_list) - calls = [call(3, 4), call(b=6)] - self.assertIn(calls, mock.call_args_list) - calls = [call(3, 4)] - self.assertIn(calls, mock.call_args_list) - - self.assertNotIn(call('fish'), mock.call_args_list) - self.assertNotIn([call('fish')], mock.call_args_list) - - - def test_call_list_str(self): - mock = Mock() - mock(1, 2) - mock.foo(a=3) - mock.foo.bar().baz('fish', cat='dog') - - expected = ( - "[call(1, 2),\n" - " call.foo(a=3),\n" - " call.foo.bar(),\n" - " call.foo.bar().baz('fish', cat='dog')]" - ) - self.assertEqual(str(mock.mock_calls), expected) - - - def test_propertymock(self): - p = patch('%s.SomeClass.one' % __name__, new_callable=PropertyMock) - mock = p.start() - try: - SomeClass.one - mock.assert_called_once_with() - - s = SomeClass() - s.one - mock.assert_called_with() - self.assertEqual(mock.mock_calls, [call(), call()]) - - s.one = 3 - self.assertEqual(mock.mock_calls, [call(), call(), call(3)]) - finally: - p.stop() - - - def test_propertymock_returnvalue(self): - m = MagicMock() - p = PropertyMock() - type(m).foo = p - - returned = m.foo - p.assert_called_once_with() - self.assertIsInstance(returned, MagicMock) - self.assertNotIsInstance(returned, PropertyMock) - - -class TestCallablePredicate(unittest.TestCase): - - def test_type(self): - for obj in [str, bytes, int, list, tuple, SomeClass]: - self.assertTrue(_callable(obj)) - - def test_call_magic_method(self): - class Callable: - def __call__(self): pass - instance = Callable() - self.assertTrue(_callable(instance)) - - def test_staticmethod(self): - class WithStaticMethod: - @staticmethod - def staticfunc(): pass - self.assertTrue(_callable(WithStaticMethod.staticfunc)) - - def test_non_callable_staticmethod(self): - class BadStaticMethod: - not_callable = staticmethod(None) - self.assertFalse(_callable(BadStaticMethod.not_callable)) - - def test_classmethod(self): - class WithClassMethod: - @classmethod - def classfunc(cls): pass - self.assertTrue(_callable(WithClassMethod.classfunc)) - - def test_non_callable_classmethod(self): - class BadClassMethod: - not_callable = classmethod(None) - self.assertFalse(_callable(BadClassMethod.not_callable)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py b/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py deleted file mode 100644 index a4feae7e9d..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testmagicmethods.py +++ /dev/null @@ -1,509 +0,0 @@ -import math -import unittest -import os -from asyncio import iscoroutinefunction -from unittest.mock import AsyncMock, Mock, MagicMock, _magics - - - -class TestMockingMagicMethods(unittest.TestCase): - - def test_deleting_magic_methods(self): - mock = Mock() - self.assertFalse(hasattr(mock, '__getitem__')) - - mock.__getitem__ = Mock() - self.assertTrue(hasattr(mock, '__getitem__')) - - del mock.__getitem__ - self.assertFalse(hasattr(mock, '__getitem__')) - - - def test_magicmock_del(self): - mock = MagicMock() - # before using getitem - del mock.__getitem__ - self.assertRaises(TypeError, lambda: mock['foo']) - - mock = MagicMock() - # this time use it first - mock['foo'] - del mock.__getitem__ - self.assertRaises(TypeError, lambda: mock['foo']) - - - def test_magic_method_wrapping(self): - mock = Mock() - def f(self, name): - return self, 'fish' - - mock.__getitem__ = f - self.assertIsNot(mock.__getitem__, f) - self.assertEqual(mock['foo'], (mock, 'fish')) - self.assertEqual(mock.__getitem__('foo'), (mock, 'fish')) - - mock.__getitem__ = mock - self.assertIs(mock.__getitem__, mock) - - - def test_magic_methods_isolated_between_mocks(self): - mock1 = Mock() - mock2 = Mock() - - mock1.__iter__ = Mock(return_value=iter([])) - self.assertEqual(list(mock1), []) - self.assertRaises(TypeError, lambda: list(mock2)) - - - def test_repr(self): - mock = Mock() - self.assertEqual(repr(mock), "" % id(mock)) - mock.__repr__ = lambda s: 'foo' - self.assertEqual(repr(mock), 'foo') - - - def test_str(self): - mock = Mock() - self.assertEqual(str(mock), object.__str__(mock)) - mock.__str__ = lambda s: 'foo' - self.assertEqual(str(mock), 'foo') - - - def test_dict_methods(self): - mock = Mock() - - self.assertRaises(TypeError, lambda: mock['foo']) - def _del(): - del mock['foo'] - def _set(): - mock['foo'] = 3 - self.assertRaises(TypeError, _del) - self.assertRaises(TypeError, _set) - - _dict = {} - def getitem(s, name): - return _dict[name] - def setitem(s, name, value): - _dict[name] = value - def delitem(s, name): - del _dict[name] - - mock.__setitem__ = setitem - mock.__getitem__ = getitem - mock.__delitem__ = delitem - - self.assertRaises(KeyError, lambda: mock['foo']) - mock['foo'] = 'bar' - self.assertEqual(_dict, {'foo': 'bar'}) - self.assertEqual(mock['foo'], 'bar') - del mock['foo'] - self.assertEqual(_dict, {}) - - - def test_numeric(self): - original = mock = Mock() - mock.value = 0 - - self.assertRaises(TypeError, lambda: mock + 3) - - def add(self, other): - mock.value += other - return self - mock.__add__ = add - self.assertEqual(mock + 3, mock) - self.assertEqual(mock.value, 3) - - del mock.__add__ - def iadd(mock): - mock += 3 - self.assertRaises(TypeError, iadd, mock) - mock.__iadd__ = add - mock += 6 - self.assertEqual(mock, original) - self.assertEqual(mock.value, 9) - - self.assertRaises(TypeError, lambda: 3 + mock) - mock.__radd__ = add - self.assertEqual(7 + mock, mock) - self.assertEqual(mock.value, 16) - - def test_division(self): - original = mock = Mock() - mock.value = 32 - self.assertRaises(TypeError, lambda: mock / 2) - - def truediv(self, other): - mock.value /= other - return self - mock.__truediv__ = truediv - self.assertEqual(mock / 2, mock) - self.assertEqual(mock.value, 16) - - del mock.__truediv__ - def itruediv(mock): - mock /= 4 - self.assertRaises(TypeError, itruediv, mock) - mock.__itruediv__ = truediv - mock /= 8 - self.assertEqual(mock, original) - self.assertEqual(mock.value, 2) - - self.assertRaises(TypeError, lambda: 8 / mock) - mock.__rtruediv__ = truediv - self.assertEqual(0.5 / mock, mock) - self.assertEqual(mock.value, 4) - - def test_hash(self): - mock = Mock() - # test delegation - self.assertEqual(hash(mock), Mock.__hash__(mock)) - - def _hash(s): - return 3 - mock.__hash__ = _hash - self.assertEqual(hash(mock), 3) - - - def test_nonzero(self): - m = Mock() - self.assertTrue(bool(m)) - - m.__bool__ = lambda s: False - self.assertFalse(bool(m)) - - - def test_comparison(self): - mock = Mock() - def comp(s, o): - return True - mock.__lt__ = mock.__gt__ = mock.__le__ = mock.__ge__ = comp - self. assertTrue(mock < 3) - self. assertTrue(mock > 3) - self. assertTrue(mock <= 3) - self. assertTrue(mock >= 3) - - self.assertRaises(TypeError, lambda: MagicMock() < object()) - self.assertRaises(TypeError, lambda: object() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > object()) - self.assertRaises(TypeError, lambda: object() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= object()) - self.assertRaises(TypeError, lambda: object() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= object()) - self.assertRaises(TypeError, lambda: object() >= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) - - - def test_equality(self): - for mock in Mock(), MagicMock(): - self.assertEqual(mock == mock, True) - self.assertIsInstance(mock == mock, bool) - self.assertEqual(mock != mock, False) - self.assertIsInstance(mock != mock, bool) - self.assertEqual(mock == object(), False) - self.assertEqual(mock != object(), True) - - def eq(self, other): - return other == 3 - mock.__eq__ = eq - self.assertTrue(mock == 3) - self.assertFalse(mock == 4) - - def ne(self, other): - return other == 3 - mock.__ne__ = ne - self.assertTrue(mock != 3) - self.assertFalse(mock != 4) - - mock = MagicMock() - mock.__eq__.return_value = True - self.assertIsInstance(mock == 3, bool) - self.assertEqual(mock == 3, True) - - mock.__ne__.return_value = False - self.assertIsInstance(mock != 3, bool) - self.assertEqual(mock != 3, False) - - - def test_len_contains_iter(self): - mock = Mock() - - self.assertRaises(TypeError, len, mock) - self.assertRaises(TypeError, iter, mock) - self.assertRaises(TypeError, lambda: 'foo' in mock) - - mock.__len__ = lambda s: 6 - self.assertEqual(len(mock), 6) - - mock.__contains__ = lambda s, o: o == 3 - self.assertIn(3, mock) - self.assertNotIn(6, mock) - - mock.__iter__ = lambda s: iter('foobarbaz') - self.assertEqual(list(mock), list('foobarbaz')) - - - def test_magicmock(self): - mock = MagicMock() - - mock.__iter__.return_value = iter([1, 2, 3]) - self.assertEqual(list(mock), [1, 2, 3]) - - getattr(mock, '__bool__').return_value = False - self.assertFalse(hasattr(mock, '__nonzero__')) - self.assertFalse(bool(mock)) - - for entry in _magics: - self.assertTrue(hasattr(mock, entry)) - self.assertFalse(hasattr(mock, '__imaginary__')) - - - def test_magic_mock_equality(self): - mock = MagicMock() - self.assertIsInstance(mock == object(), bool) - self.assertIsInstance(mock != object(), bool) - - self.assertEqual(mock == object(), False) - self.assertEqual(mock != object(), True) - self.assertEqual(mock == mock, True) - self.assertEqual(mock != mock, False) - - def test_asyncmock_defaults(self): - mock = AsyncMock() - self.assertEqual(int(mock), 1) - self.assertEqual(complex(mock), 1j) - self.assertEqual(float(mock), 1.0) - self.assertNotIn(object(), mock) - self.assertEqual(len(mock), 0) - self.assertEqual(list(mock), []) - self.assertEqual(hash(mock), object.__hash__(mock)) - self.assertEqual(str(mock), object.__str__(mock)) - self.assertTrue(bool(mock)) - self.assertEqual(round(mock), mock.__round__()) - self.assertEqual(math.trunc(mock), mock.__trunc__()) - self.assertEqual(math.floor(mock), mock.__floor__()) - self.assertEqual(math.ceil(mock), mock.__ceil__()) - self.assertTrue(iscoroutinefunction(mock.__aexit__)) - self.assertTrue(iscoroutinefunction(mock.__aenter__)) - self.assertIsInstance(mock.__aenter__, AsyncMock) - self.assertIsInstance(mock.__aexit__, AsyncMock) - - # in Python 3 oct and hex use __index__ - # so these tests are for __index__ in py3k - self.assertEqual(oct(mock), '0o1') - self.assertEqual(hex(mock), '0x1') - # how to test __sizeof__ ? - - def test_magicmock_defaults(self): - mock = MagicMock() - self.assertEqual(int(mock), 1) - self.assertEqual(complex(mock), 1j) - self.assertEqual(float(mock), 1.0) - self.assertNotIn(object(), mock) - self.assertEqual(len(mock), 0) - self.assertEqual(list(mock), []) - self.assertEqual(hash(mock), object.__hash__(mock)) - self.assertEqual(str(mock), object.__str__(mock)) - self.assertTrue(bool(mock)) - self.assertEqual(round(mock), mock.__round__()) - self.assertEqual(math.trunc(mock), mock.__trunc__()) - self.assertEqual(math.floor(mock), mock.__floor__()) - self.assertEqual(math.ceil(mock), mock.__ceil__()) - self.assertTrue(iscoroutinefunction(mock.__aexit__)) - self.assertTrue(iscoroutinefunction(mock.__aenter__)) - self.assertIsInstance(mock.__aenter__, AsyncMock) - self.assertIsInstance(mock.__aexit__, AsyncMock) - - # in Python 3 oct and hex use __index__ - # so these tests are for __index__ in py3k - self.assertEqual(oct(mock), '0o1') - self.assertEqual(hex(mock), '0x1') - # how to test __sizeof__ ? - - - def test_magic_methods_fspath(self): - mock = MagicMock() - expected_path = mock.__fspath__() - mock.reset_mock() - - self.assertEqual(os.fspath(mock), expected_path) - mock.__fspath__.assert_called_once() - - - def test_magic_methods_and_spec(self): - class Iterable(object): - def __iter__(self): pass - - mock = Mock(spec=Iterable) - self.assertRaises(AttributeError, lambda: mock.__iter__) - - mock.__iter__ = Mock(return_value=iter([])) - self.assertEqual(list(mock), []) - - class NonIterable(object): - pass - mock = Mock(spec=NonIterable) - self.assertRaises(AttributeError, lambda: mock.__iter__) - - def set_int(): - mock.__int__ = Mock(return_value=iter([])) - self.assertRaises(AttributeError, set_int) - - mock = MagicMock(spec=Iterable) - self.assertEqual(list(mock), []) - self.assertRaises(AttributeError, set_int) - - - def test_magic_methods_and_spec_set(self): - class Iterable(object): - def __iter__(self): pass - - mock = Mock(spec_set=Iterable) - self.assertRaises(AttributeError, lambda: mock.__iter__) - - mock.__iter__ = Mock(return_value=iter([])) - self.assertEqual(list(mock), []) - - class NonIterable(object): - pass - mock = Mock(spec_set=NonIterable) - self.assertRaises(AttributeError, lambda: mock.__iter__) - - def set_int(): - mock.__int__ = Mock(return_value=iter([])) - self.assertRaises(AttributeError, set_int) - - mock = MagicMock(spec_set=Iterable) - self.assertEqual(list(mock), []) - self.assertRaises(AttributeError, set_int) - - - def test_setting_unsupported_magic_method(self): - mock = MagicMock() - def set_setattr(): - mock.__setattr__ = lambda self, name: None - self.assertRaisesRegex(AttributeError, - "Attempting to set unsupported magic method '__setattr__'.", - set_setattr - ) - - - def test_attributes_and_return_value(self): - mock = MagicMock() - attr = mock.foo - def _get_type(obj): - # the type of every mock (or magicmock) is a custom subclass - # so the real type is the second in the mro - return type(obj).__mro__[1] - self.assertEqual(_get_type(attr), MagicMock) - - returned = mock() - self.assertEqual(_get_type(returned), MagicMock) - - - def test_magic_methods_are_magic_mocks(self): - mock = MagicMock() - self.assertIsInstance(mock.__getitem__, MagicMock) - - mock[1][2].__getitem__.return_value = 3 - self.assertEqual(mock[1][2][3], 3) - - - def test_magic_method_reset_mock(self): - mock = MagicMock() - str(mock) - self.assertTrue(mock.__str__.called) - mock.reset_mock() - self.assertFalse(mock.__str__.called) - - - def test_dir(self): - # overriding the default implementation - for mock in Mock(), MagicMock(): - def _dir(self): - return ['foo'] - mock.__dir__ = _dir - self.assertEqual(dir(mock), ['foo']) - - - def test_bound_methods(self): - m = Mock() - - # XXXX should this be an expected failure instead? - - # this seems like it should work, but is hard to do without introducing - # other api inconsistencies. Failure message could be better though. - m.__iter__ = [3].__iter__ - self.assertRaises(TypeError, iter, m) - - - def test_magic_method_type(self): - class Foo(MagicMock): - pass - - foo = Foo() - self.assertIsInstance(foo.__int__, Foo) - - - def test_descriptor_from_class(self): - m = MagicMock() - type(m).__str__.return_value = 'foo' - self.assertEqual(str(m), 'foo') - - - def test_iterable_as_iter_return_value(self): - m = MagicMock() - m.__iter__.return_value = [1, 2, 3] - self.assertEqual(list(m), [1, 2, 3]) - self.assertEqual(list(m), [1, 2, 3]) - - m.__iter__.return_value = iter([4, 5, 6]) - self.assertEqual(list(m), [4, 5, 6]) - self.assertEqual(list(m), []) - - - def test_matmul(self): - m = MagicMock() - self.assertIsInstance(m @ 1, MagicMock) - m.__matmul__.return_value = 42 - m.__rmatmul__.return_value = 666 - m.__imatmul__.return_value = 24 - self.assertEqual(m @ 1, 42) - self.assertEqual(1 @ m, 666) - m @= 24 - self.assertEqual(m, 24) - - def test_divmod_and_rdivmod(self): - m = MagicMock() - self.assertIsInstance(divmod(5, m), MagicMock) - m.__divmod__.return_value = (2, 1) - self.assertEqual(divmod(m, 2), (2, 1)) - m = MagicMock() - foo = divmod(2, m) - self.assertIsInstance(foo, MagicMock) - foo_direct = m.__divmod__(2) - self.assertIsInstance(foo_direct, MagicMock) - bar = divmod(m, 2) - self.assertIsInstance(bar, MagicMock) - bar_direct = m.__rdivmod__(2) - self.assertIsInstance(bar_direct, MagicMock) - - # http://bugs.python.org/issue23310 - # Check if you can change behaviour of magic methods in MagicMock init - def test_magic_in_initialization(self): - m = MagicMock(**{'__str__.return_value': "12"}) - self.assertEqual(str(m), "12") - - def test_changing_magic_set_in_initialization(self): - m = MagicMock(**{'__str__.return_value': "12"}) - m.__str__.return_value = "13" - self.assertEqual(str(m), "13") - m = MagicMock(**{'__str__.return_value': "12"}) - m.configure_mock(**{'__str__.return_value': "14"}) - self.assertEqual(str(m), "14") - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py b/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py deleted file mode 100644 index f930724530..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testmock.py +++ /dev/null @@ -1,2171 +0,0 @@ -import copy -import re -import sys -import tempfile - -from test.support import ALWAYS_EQ -import unittest -from unittest.test.testmock.support import is_instance -from unittest import mock -from unittest.mock import ( - call, DEFAULT, patch, sentinel, - MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, AsyncMock, _Call, _CallList, - create_autospec -) - - -class Iter(object): - def __init__(self): - self.thing = iter(['this', 'is', 'an', 'iter']) - - def __iter__(self): - return self - - def next(self): - return next(self.thing) - - __next__ = next - - -class Something(object): - def meth(self, a, b, c, d=None): pass - - @classmethod - def cmeth(cls, a, b, c, d=None): pass - - @staticmethod - def smeth(a, b, c, d=None): pass - - -def something(a): pass - - -class MockTest(unittest.TestCase): - - def test_all(self): - # if __all__ is badly defined then import * will raise an error - # We have to exec it because you can't import * inside a method - # in Python 3 - exec("from unittest.mock import *") - - - def test_constructor(self): - mock = Mock() - - self.assertFalse(mock.called, "called not initialised correctly") - self.assertEqual(mock.call_count, 0, - "call_count not initialised correctly") - self.assertTrue(is_instance(mock.return_value, Mock), - "return_value not initialised correctly") - - self.assertEqual(mock.call_args, None, - "call_args not initialised correctly") - self.assertEqual(mock.call_args_list, [], - "call_args_list not initialised correctly") - self.assertEqual(mock.method_calls, [], - "method_calls not initialised correctly") - - # Can't use hasattr for this test as it always returns True on a mock - self.assertNotIn('_items', mock.__dict__, - "default mock should not have '_items' attribute") - - self.assertIsNone(mock._mock_parent, - "parent not initialised correctly") - self.assertIsNone(mock._mock_methods, - "methods not initialised correctly") - self.assertEqual(mock._mock_children, {}, - "children not initialised incorrectly") - - - def test_return_value_in_constructor(self): - mock = Mock(return_value=None) - self.assertIsNone(mock.return_value, - "return value in constructor not honoured") - - - def test_change_return_value_via_delegate(self): - def f(): pass - mock = create_autospec(f) - mock.mock.return_value = 1 - self.assertEqual(mock(), 1) - - - def test_change_side_effect_via_delegate(self): - def f(): pass - mock = create_autospec(f) - mock.mock.side_effect = TypeError() - with self.assertRaises(TypeError): - mock() - - - def test_repr(self): - mock = Mock(name='foo') - self.assertIn('foo', repr(mock)) - self.assertIn("'%s'" % id(mock), repr(mock)) - - mocks = [(Mock(), 'mock'), (Mock(name='bar'), 'bar')] - for mock, name in mocks: - self.assertIn('%s.bar' % name, repr(mock.bar)) - self.assertIn('%s.foo()' % name, repr(mock.foo())) - self.assertIn('%s.foo().bing' % name, repr(mock.foo().bing)) - self.assertIn('%s()' % name, repr(mock())) - self.assertIn('%s()()' % name, repr(mock()())) - self.assertIn('%s()().foo.bar.baz().bing' % name, - repr(mock()().foo.bar.baz().bing)) - - - def test_repr_with_spec(self): - class X(object): - pass - - mock = Mock(spec=X) - self.assertIn(" spec='X' ", repr(mock)) - - mock = Mock(spec=X()) - self.assertIn(" spec='X' ", repr(mock)) - - mock = Mock(spec_set=X) - self.assertIn(" spec_set='X' ", repr(mock)) - - mock = Mock(spec_set=X()) - self.assertIn(" spec_set='X' ", repr(mock)) - - mock = Mock(spec=X, name='foo') - self.assertIn(" spec='X' ", repr(mock)) - self.assertIn(" name='foo' ", repr(mock)) - - mock = Mock(name='foo') - self.assertNotIn("spec", repr(mock)) - - mock = Mock() - self.assertNotIn("spec", repr(mock)) - - mock = Mock(spec=['foo']) - self.assertNotIn("spec", repr(mock)) - - - def test_side_effect(self): - mock = Mock() - - def effect(*args, **kwargs): - raise SystemError('kablooie') - - mock.side_effect = effect - self.assertRaises(SystemError, mock, 1, 2, fish=3) - mock.assert_called_with(1, 2, fish=3) - - results = [1, 2, 3] - def effect(): - return results.pop() - mock.side_effect = effect - - self.assertEqual([mock(), mock(), mock()], [3, 2, 1], - "side effect not used correctly") - - mock = Mock(side_effect=sentinel.SideEffect) - self.assertEqual(mock.side_effect, sentinel.SideEffect, - "side effect in constructor not used") - - def side_effect(): - return DEFAULT - mock = Mock(side_effect=side_effect, return_value=sentinel.RETURN) - self.assertEqual(mock(), sentinel.RETURN) - - def test_autospec_side_effect(self): - # Test for issue17826 - results = [1, 2, 3] - def effect(): - return results.pop() - def f(): pass - - mock = create_autospec(f) - mock.side_effect = [1, 2, 3] - self.assertEqual([mock(), mock(), mock()], [1, 2, 3], - "side effect not used correctly in create_autospec") - # Test where side effect is a callable - results = [1, 2, 3] - mock = create_autospec(f) - mock.side_effect = effect - self.assertEqual([mock(), mock(), mock()], [3, 2, 1], - "callable side effect not used correctly") - - def test_autospec_side_effect_exception(self): - # Test for issue 23661 - def f(): pass - - mock = create_autospec(f) - mock.side_effect = ValueError('Bazinga!') - self.assertRaisesRegex(ValueError, 'Bazinga!', mock) - - - def test_reset_mock(self): - parent = Mock() - spec = ["something"] - mock = Mock(name="child", parent=parent, spec=spec) - mock(sentinel.Something, something=sentinel.SomethingElse) - something = mock.something - mock.something() - mock.side_effect = sentinel.SideEffect - return_value = mock.return_value - return_value() - - mock.reset_mock() - - self.assertEqual(mock._mock_name, "child", - "name incorrectly reset") - self.assertEqual(mock._mock_parent, parent, - "parent incorrectly reset") - self.assertEqual(mock._mock_methods, spec, - "methods incorrectly reset") - - self.assertFalse(mock.called, "called not reset") - self.assertEqual(mock.call_count, 0, "call_count not reset") - self.assertEqual(mock.call_args, None, "call_args not reset") - self.assertEqual(mock.call_args_list, [], "call_args_list not reset") - self.assertEqual(mock.method_calls, [], - "method_calls not initialised correctly: %r != %r" % - (mock.method_calls, [])) - self.assertEqual(mock.mock_calls, []) - - self.assertEqual(mock.side_effect, sentinel.SideEffect, - "side_effect incorrectly reset") - self.assertEqual(mock.return_value, return_value, - "return_value incorrectly reset") - self.assertFalse(return_value.called, "return value mock not reset") - self.assertEqual(mock._mock_children, {'something': something}, - "children reset incorrectly") - self.assertEqual(mock.something, something, - "children incorrectly cleared") - self.assertFalse(mock.something.called, "child not reset") - - - def test_reset_mock_recursion(self): - mock = Mock() - mock.return_value = mock - - # used to cause recursion - mock.reset_mock() - - def test_reset_mock_on_mock_open_issue_18622(self): - a = mock.mock_open() - a.reset_mock() - - def test_call(self): - mock = Mock() - self.assertTrue(is_instance(mock.return_value, Mock), - "Default return_value should be a Mock") - - result = mock() - self.assertEqual(mock(), result, - "different result from consecutive calls") - mock.reset_mock() - - ret_val = mock(sentinel.Arg) - self.assertTrue(mock.called, "called not set") - self.assertEqual(mock.call_count, 1, "call_count incorrect") - self.assertEqual(mock.call_args, ((sentinel.Arg,), {}), - "call_args not set") - self.assertEqual(mock.call_args.args, (sentinel.Arg,), - "call_args not set") - self.assertEqual(mock.call_args.kwargs, {}, - "call_args not set") - self.assertEqual(mock.call_args_list, [((sentinel.Arg,), {})], - "call_args_list not initialised correctly") - - mock.return_value = sentinel.ReturnValue - ret_val = mock(sentinel.Arg, key=sentinel.KeyArg) - self.assertEqual(ret_val, sentinel.ReturnValue, - "incorrect return value") - - self.assertEqual(mock.call_count, 2, "call_count incorrect") - self.assertEqual(mock.call_args, - ((sentinel.Arg,), {'key': sentinel.KeyArg}), - "call_args not set") - self.assertEqual(mock.call_args_list, [ - ((sentinel.Arg,), {}), - ((sentinel.Arg,), {'key': sentinel.KeyArg}) - ], - "call_args_list not set") - - - def test_call_args_comparison(self): - mock = Mock() - mock() - mock(sentinel.Arg) - mock(kw=sentinel.Kwarg) - mock(sentinel.Arg, kw=sentinel.Kwarg) - self.assertEqual(mock.call_args_list, [ - (), - ((sentinel.Arg,),), - ({"kw": sentinel.Kwarg},), - ((sentinel.Arg,), {"kw": sentinel.Kwarg}) - ]) - self.assertEqual(mock.call_args, - ((sentinel.Arg,), {"kw": sentinel.Kwarg})) - self.assertEqual(mock.call_args.args, (sentinel.Arg,)) - self.assertEqual(mock.call_args.kwargs, {"kw": sentinel.Kwarg}) - - # Comparing call_args to a long sequence should not raise - # an exception. See issue 24857. - self.assertFalse(mock.call_args == "a long sequence") - - - def test_calls_equal_with_any(self): - # Check that equality and non-equality is consistent even when - # comparing with mock.ANY - mm = mock.MagicMock() - self.assertTrue(mm == mm) - self.assertFalse(mm != mm) - self.assertFalse(mm == mock.MagicMock()) - self.assertTrue(mm != mock.MagicMock()) - self.assertTrue(mm == mock.ANY) - self.assertFalse(mm != mock.ANY) - self.assertTrue(mock.ANY == mm) - self.assertFalse(mock.ANY != mm) - self.assertTrue(mm == ALWAYS_EQ) - self.assertFalse(mm != ALWAYS_EQ) - - call1 = mock.call(mock.MagicMock()) - call2 = mock.call(mock.ANY) - self.assertTrue(call1 == call2) - self.assertFalse(call1 != call2) - self.assertTrue(call2 == call1) - self.assertFalse(call2 != call1) - - self.assertTrue(call1 == ALWAYS_EQ) - self.assertFalse(call1 != ALWAYS_EQ) - self.assertFalse(call1 == 1) - self.assertTrue(call1 != 1) - - - def test_assert_called_with(self): - mock = Mock() - mock() - - # Will raise an exception if it fails - mock.assert_called_with() - self.assertRaises(AssertionError, mock.assert_called_with, 1) - - mock.reset_mock() - self.assertRaises(AssertionError, mock.assert_called_with) - - mock(1, 2, 3, a='fish', b='nothing') - mock.assert_called_with(1, 2, 3, a='fish', b='nothing') - - - def test_assert_called_with_any(self): - m = MagicMock() - m(MagicMock()) - m.assert_called_with(mock.ANY) - - - def test_assert_called_with_function_spec(self): - def f(a, b, c, d=None): pass - - mock = Mock(spec=f) - - mock(1, b=2, c=3) - mock.assert_called_with(1, 2, 3) - mock.assert_called_with(a=1, b=2, c=3) - self.assertRaises(AssertionError, mock.assert_called_with, - 1, b=3, c=2) - # Expected call doesn't match the spec's signature - with self.assertRaises(AssertionError) as cm: - mock.assert_called_with(e=8) - self.assertIsInstance(cm.exception.__cause__, TypeError) - - - def test_assert_called_with_method_spec(self): - def _check(mock): - mock(1, b=2, c=3) - mock.assert_called_with(1, 2, 3) - mock.assert_called_with(a=1, b=2, c=3) - self.assertRaises(AssertionError, mock.assert_called_with, - 1, b=3, c=2) - - mock = Mock(spec=Something().meth) - _check(mock) - mock = Mock(spec=Something.cmeth) - _check(mock) - mock = Mock(spec=Something().cmeth) - _check(mock) - mock = Mock(spec=Something.smeth) - _check(mock) - mock = Mock(spec=Something().smeth) - _check(mock) - - - def test_assert_called_exception_message(self): - msg = "Expected '{0}' to have been called" - with self.assertRaisesRegex(AssertionError, msg.format('mock')): - Mock().assert_called() - with self.assertRaisesRegex(AssertionError, msg.format('test_name')): - Mock(name="test_name").assert_called() - - - def test_assert_called_once_with(self): - mock = Mock() - mock() - - # Will raise an exception if it fails - mock.assert_called_once_with() - - mock() - self.assertRaises(AssertionError, mock.assert_called_once_with) - - mock.reset_mock() - self.assertRaises(AssertionError, mock.assert_called_once_with) - - mock('foo', 'bar', baz=2) - mock.assert_called_once_with('foo', 'bar', baz=2) - - mock.reset_mock() - mock('foo', 'bar', baz=2) - self.assertRaises( - AssertionError, - lambda: mock.assert_called_once_with('bob', 'bar', baz=2) - ) - - def test_assert_called_once_with_call_list(self): - m = Mock() - m(1) - m(2) - self.assertRaisesRegex(AssertionError, - re.escape("Calls: [call(1), call(2)]"), - lambda: m.assert_called_once_with(2)) - - - def test_assert_called_once_with_function_spec(self): - def f(a, b, c, d=None): pass - - mock = Mock(spec=f) - - mock(1, b=2, c=3) - mock.assert_called_once_with(1, 2, 3) - mock.assert_called_once_with(a=1, b=2, c=3) - self.assertRaises(AssertionError, mock.assert_called_once_with, - 1, b=3, c=2) - # Expected call doesn't match the spec's signature - with self.assertRaises(AssertionError) as cm: - mock.assert_called_once_with(e=8) - self.assertIsInstance(cm.exception.__cause__, TypeError) - # Mock called more than once => always fails - mock(4, 5, 6) - self.assertRaises(AssertionError, mock.assert_called_once_with, - 1, 2, 3) - self.assertRaises(AssertionError, mock.assert_called_once_with, - 4, 5, 6) - - - def test_attribute_access_returns_mocks(self): - mock = Mock() - something = mock.something - self.assertTrue(is_instance(something, Mock), "attribute isn't a mock") - self.assertEqual(mock.something, something, - "different attributes returned for same name") - - # Usage example - mock = Mock() - mock.something.return_value = 3 - - self.assertEqual(mock.something(), 3, "method returned wrong value") - self.assertTrue(mock.something.called, - "method didn't record being called") - - - def test_attributes_have_name_and_parent_set(self): - mock = Mock() - something = mock.something - - self.assertEqual(something._mock_name, "something", - "attribute name not set correctly") - self.assertEqual(something._mock_parent, mock, - "attribute parent not set correctly") - - - def test_method_calls_recorded(self): - mock = Mock() - mock.something(3, fish=None) - mock.something_else.something(6, cake=sentinel.Cake) - - self.assertEqual(mock.something_else.method_calls, - [("something", (6,), {'cake': sentinel.Cake})], - "method calls not recorded correctly") - self.assertEqual(mock.method_calls, [ - ("something", (3,), {'fish': None}), - ("something_else.something", (6,), {'cake': sentinel.Cake}) - ], - "method calls not recorded correctly") - - - def test_method_calls_compare_easily(self): - mock = Mock() - mock.something() - self.assertEqual(mock.method_calls, [('something',)]) - self.assertEqual(mock.method_calls, [('something', (), {})]) - - mock = Mock() - mock.something('different') - self.assertEqual(mock.method_calls, [('something', ('different',))]) - self.assertEqual(mock.method_calls, - [('something', ('different',), {})]) - - mock = Mock() - mock.something(x=1) - self.assertEqual(mock.method_calls, [('something', {'x': 1})]) - self.assertEqual(mock.method_calls, [('something', (), {'x': 1})]) - - mock = Mock() - mock.something('different', some='more') - self.assertEqual(mock.method_calls, [ - ('something', ('different',), {'some': 'more'}) - ]) - - - def test_only_allowed_methods_exist(self): - for spec in ['something'], ('something',): - for arg in 'spec', 'spec_set': - mock = Mock(**{arg: spec}) - - # this should be allowed - mock.something - self.assertRaisesRegex( - AttributeError, - "Mock object has no attribute 'something_else'", - getattr, mock, 'something_else' - ) - - - def test_from_spec(self): - class Something(object): - x = 3 - __something__ = None - def y(self): pass - - def test_attributes(mock): - # should work - mock.x - mock.y - mock.__something__ - self.assertRaisesRegex( - AttributeError, - "Mock object has no attribute 'z'", - getattr, mock, 'z' - ) - self.assertRaisesRegex( - AttributeError, - "Mock object has no attribute '__foobar__'", - getattr, mock, '__foobar__' - ) - - test_attributes(Mock(spec=Something)) - test_attributes(Mock(spec=Something())) - - - def test_wraps_calls(self): - real = Mock() - - mock = Mock(wraps=real) - self.assertEqual(mock(), real()) - - real.reset_mock() - - mock(1, 2, fish=3) - real.assert_called_with(1, 2, fish=3) - - - def test_wraps_prevents_automatic_creation_of_mocks(self): - class Real(object): - pass - - real = Real() - mock = Mock(wraps=real) - - self.assertRaises(AttributeError, lambda: mock.new_attr()) - - - def test_wraps_call_with_nondefault_return_value(self): - real = Mock() - - mock = Mock(wraps=real) - mock.return_value = 3 - - self.assertEqual(mock(), 3) - self.assertFalse(real.called) - - - def test_wraps_attributes(self): - class Real(object): - attribute = Mock() - - real = Real() - - mock = Mock(wraps=real) - self.assertEqual(mock.attribute(), real.attribute()) - self.assertRaises(AttributeError, lambda: mock.fish) - - self.assertNotEqual(mock.attribute, real.attribute) - result = mock.attribute.frog(1, 2, fish=3) - Real.attribute.frog.assert_called_with(1, 2, fish=3) - self.assertEqual(result, Real.attribute.frog()) - - - def test_customize_wrapped_object_with_side_effect_iterable_with_default(self): - class Real(object): - def method(self): - return sentinel.ORIGINAL_VALUE - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = [sentinel.VALUE1, DEFAULT] - - self.assertEqual(mock.method(), sentinel.VALUE1) - self.assertEqual(mock.method(), sentinel.ORIGINAL_VALUE) - self.assertRaises(StopIteration, mock.method) - - - def test_customize_wrapped_object_with_side_effect_iterable(self): - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] - - self.assertEqual(mock.method(), sentinel.VALUE1) - self.assertEqual(mock.method(), sentinel.VALUE2) - self.assertRaises(StopIteration, mock.method) - - - def test_customize_wrapped_object_with_side_effect_exception(self): - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = RuntimeError - - self.assertRaises(RuntimeError, mock.method) - - - def test_customize_wrapped_object_with_side_effect_function(self): - class Real(object): - def method(self): pass - def side_effect(): - return sentinel.VALUE - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = side_effect - - self.assertEqual(mock.method(), sentinel.VALUE) - - - def test_customize_wrapped_object_with_return_value(self): - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.return_value = sentinel.VALUE - - self.assertEqual(mock.method(), sentinel.VALUE) - - - def test_customize_wrapped_object_with_return_value_and_side_effect(self): - # side_effect should always take precedence over return_value. - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] - mock.method.return_value = sentinel.WRONG_VALUE - - self.assertEqual(mock.method(), sentinel.VALUE1) - self.assertEqual(mock.method(), sentinel.VALUE2) - self.assertRaises(StopIteration, mock.method) - - - def test_customize_wrapped_object_with_return_value_and_side_effect2(self): - # side_effect can return DEFAULT to default to return_value - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = lambda: DEFAULT - mock.method.return_value = sentinel.VALUE - - self.assertEqual(mock.method(), sentinel.VALUE) - - - def test_customize_wrapped_object_with_return_value_and_side_effect_default(self): - class Real(object): - def method(self): pass - - real = Real() - mock = Mock(wraps=real) - mock.method.side_effect = [sentinel.VALUE1, DEFAULT] - mock.method.return_value = sentinel.RETURN - - self.assertEqual(mock.method(), sentinel.VALUE1) - self.assertEqual(mock.method(), sentinel.RETURN) - self.assertRaises(StopIteration, mock.method) - - - def test_magic_method_wraps_dict(self): - # bpo-25597: MagicMock with wrap doesn't call wrapped object's - # method for magic methods with default values. - data = {'foo': 'bar'} - - wrapped_dict = MagicMock(wraps=data) - self.assertEqual(wrapped_dict.get('foo'), 'bar') - # Accessing key gives a MagicMock - self.assertIsInstance(wrapped_dict['foo'], MagicMock) - # __contains__ method has a default value of False - self.assertFalse('foo' in wrapped_dict) - - # return_value is non-sentinel and takes precedence over wrapped value. - wrapped_dict.get.return_value = 'return_value' - self.assertEqual(wrapped_dict.get('foo'), 'return_value') - - # return_value is sentinel and hence wrapped value is returned. - wrapped_dict.get.return_value = sentinel.DEFAULT - self.assertEqual(wrapped_dict.get('foo'), 'bar') - - self.assertEqual(wrapped_dict.get('baz'), None) - self.assertIsInstance(wrapped_dict['baz'], MagicMock) - self.assertFalse('bar' in wrapped_dict) - - data['baz'] = 'spam' - self.assertEqual(wrapped_dict.get('baz'), 'spam') - self.assertIsInstance(wrapped_dict['baz'], MagicMock) - self.assertFalse('bar' in wrapped_dict) - - del data['baz'] - self.assertEqual(wrapped_dict.get('baz'), None) - - - def test_magic_method_wraps_class(self): - - class Foo: - - def __getitem__(self, index): - return index - - def __custom_method__(self): - return "foo" - - - klass = MagicMock(wraps=Foo) - obj = klass() - self.assertEqual(obj.__getitem__(2), 2) - self.assertEqual(obj[2], 2) - self.assertEqual(obj.__custom_method__(), "foo") - - - def test_exceptional_side_effect(self): - mock = Mock(side_effect=AttributeError) - self.assertRaises(AttributeError, mock) - - mock = Mock(side_effect=AttributeError('foo')) - self.assertRaises(AttributeError, mock) - - - def test_baseexceptional_side_effect(self): - mock = Mock(side_effect=KeyboardInterrupt) - self.assertRaises(KeyboardInterrupt, mock) - - mock = Mock(side_effect=KeyboardInterrupt('foo')) - self.assertRaises(KeyboardInterrupt, mock) - - - def test_assert_called_with_message(self): - mock = Mock() - self.assertRaisesRegex(AssertionError, 'not called', - mock.assert_called_with) - - - def test_assert_called_once_with_message(self): - mock = Mock(name='geoffrey') - self.assertRaisesRegex(AssertionError, - r"Expected 'geoffrey' to be called once\.", - mock.assert_called_once_with) - - - def test__name__(self): - mock = Mock() - self.assertRaises(AttributeError, lambda: mock.__name__) - - mock.__name__ = 'foo' - self.assertEqual(mock.__name__, 'foo') - - - def test_spec_list_subclass(self): - class Sub(list): - pass - mock = Mock(spec=Sub(['foo'])) - - mock.append(3) - mock.append.assert_called_with(3) - self.assertRaises(AttributeError, getattr, mock, 'foo') - - - def test_spec_class(self): - class X(object): - pass - - mock = Mock(spec=X) - self.assertIsInstance(mock, X) - - mock = Mock(spec=X()) - self.assertIsInstance(mock, X) - - self.assertIs(mock.__class__, X) - self.assertEqual(Mock().__class__.__name__, 'Mock') - - mock = Mock(spec_set=X) - self.assertIsInstance(mock, X) - - mock = Mock(spec_set=X()) - self.assertIsInstance(mock, X) - - - def test_spec_class_no_object_base(self): - class X: - pass - - mock = Mock(spec=X) - self.assertIsInstance(mock, X) - - mock = Mock(spec=X()) - self.assertIsInstance(mock, X) - - self.assertIs(mock.__class__, X) - self.assertEqual(Mock().__class__.__name__, 'Mock') - - mock = Mock(spec_set=X) - self.assertIsInstance(mock, X) - - mock = Mock(spec_set=X()) - self.assertIsInstance(mock, X) - - - def test_setting_attribute_with_spec_set(self): - class X(object): - y = 3 - - mock = Mock(spec=X) - mock.x = 'foo' - - mock = Mock(spec_set=X) - def set_attr(): - mock.x = 'foo' - - mock.y = 'foo' - self.assertRaises(AttributeError, set_attr) - - - def test_copy(self): - current = sys.getrecursionlimit() - self.addCleanup(sys.setrecursionlimit, current) - - # can't use sys.maxint as this doesn't exist in Python 3 - sys.setrecursionlimit(int(10e8)) - # this segfaults without the fix in place - copy.copy(Mock()) - - - def test_subclass_with_properties(self): - class SubClass(Mock): - def _get(self): - return 3 - def _set(self, value): - raise NameError('strange error') - some_attribute = property(_get, _set) - - s = SubClass(spec_set=SubClass) - self.assertEqual(s.some_attribute, 3) - - def test(): - s.some_attribute = 3 - self.assertRaises(NameError, test) - - def test(): - s.foo = 'bar' - self.assertRaises(AttributeError, test) - - - def test_setting_call(self): - mock = Mock() - def __call__(self, a): - self._increment_mock_call(a) - return self._mock_call(a) - - type(mock).__call__ = __call__ - mock('one') - mock.assert_called_with('one') - - self.assertRaises(TypeError, mock, 'one', 'two') - - - def test_dir(self): - mock = Mock() - attrs = set(dir(mock)) - type_attrs = set([m for m in dir(Mock) if not m.startswith('_')]) - - # all public attributes from the type are included - self.assertEqual(set(), type_attrs - attrs) - - # creates these attributes - mock.a, mock.b - self.assertIn('a', dir(mock)) - self.assertIn('b', dir(mock)) - - # instance attributes - mock.c = mock.d = None - self.assertIn('c', dir(mock)) - self.assertIn('d', dir(mock)) - - # magic methods - mock.__iter__ = lambda s: iter([]) - self.assertIn('__iter__', dir(mock)) - - - def test_dir_from_spec(self): - mock = Mock(spec=unittest.TestCase) - testcase_attrs = set(dir(unittest.TestCase)) - attrs = set(dir(mock)) - - # all attributes from the spec are included - self.assertEqual(set(), testcase_attrs - attrs) - - # shadow a sys attribute - mock.version = 3 - self.assertEqual(dir(mock).count('version'), 1) - - - def test_filter_dir(self): - patcher = patch.object(mock, 'FILTER_DIR', False) - patcher.start() - try: - attrs = set(dir(Mock())) - type_attrs = set(dir(Mock)) - - # ALL attributes from the type are included - self.assertEqual(set(), type_attrs - attrs) - finally: - patcher.stop() - - - def test_dir_does_not_include_deleted_attributes(self): - mock = Mock() - mock.child.return_value = 1 - - self.assertIn('child', dir(mock)) - del mock.child - self.assertNotIn('child', dir(mock)) - - - def test_configure_mock(self): - mock = Mock(foo='bar') - self.assertEqual(mock.foo, 'bar') - - mock = MagicMock(foo='bar') - self.assertEqual(mock.foo, 'bar') - - kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, - 'foo': MagicMock()} - mock = Mock(**kwargs) - self.assertRaises(KeyError, mock) - self.assertEqual(mock.foo.bar(), 33) - self.assertIsInstance(mock.foo, MagicMock) - - mock = Mock() - mock.configure_mock(**kwargs) - self.assertRaises(KeyError, mock) - self.assertEqual(mock.foo.bar(), 33) - self.assertIsInstance(mock.foo, MagicMock) - - - def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): - # needed because assertRaisesRegex doesn't work easily with newlines - with self.assertRaises(exception) as context: - func(*args, **kwargs) - msg = str(context.exception) - self.assertEqual(msg, message) - - - def test_assert_called_with_failure_message(self): - mock = NonCallableMock() - - actual = 'not called.' - expected = "mock(1, '2', 3, bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' - self.assertRaisesWithMsg( - AssertionError, message % (expected, actual), - mock.assert_called_with, 1, '2', 3, bar='foo' - ) - - mock.foo(1, '2', 3, foo='foo') - - - asserters = [ - mock.foo.assert_called_with, mock.foo.assert_called_once_with - ] - for meth in asserters: - actual = "foo(1, '2', 3, foo='foo')" - expected = "foo(1, '2', 3, bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' - self.assertRaisesWithMsg( - AssertionError, message % (expected, actual), - meth, 1, '2', 3, bar='foo' - ) - - # just kwargs - for meth in asserters: - actual = "foo(1, '2', 3, foo='foo')" - expected = "foo(bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' - self.assertRaisesWithMsg( - AssertionError, message % (expected, actual), - meth, bar='foo' - ) - - # just args - for meth in asserters: - actual = "foo(1, '2', 3, foo='foo')" - expected = "foo(1, 2, 3)" - message = 'expected call not found.\nExpected: %s\nActual: %s' - self.assertRaisesWithMsg( - AssertionError, message % (expected, actual), - meth, 1, 2, 3 - ) - - # empty - for meth in asserters: - actual = "foo(1, '2', 3, foo='foo')" - expected = "foo()" - message = 'expected call not found.\nExpected: %s\nActual: %s' - self.assertRaisesWithMsg( - AssertionError, message % (expected, actual), meth - ) - - - def test_mock_calls(self): - mock = MagicMock() - - # need to do this because MagicMock.mock_calls used to just return - # a MagicMock which also returned a MagicMock when __eq__ was called - self.assertIs(mock.mock_calls == [], True) - - mock = MagicMock() - mock() - expected = [('', (), {})] - self.assertEqual(mock.mock_calls, expected) - - mock.foo() - expected.append(call.foo()) - self.assertEqual(mock.mock_calls, expected) - # intermediate mock_calls work too - self.assertEqual(mock.foo.mock_calls, [('', (), {})]) - - mock = MagicMock() - mock().foo(1, 2, 3, a=4, b=5) - expected = [ - ('', (), {}), ('().foo', (1, 2, 3), dict(a=4, b=5)) - ] - self.assertEqual(mock.mock_calls, expected) - self.assertEqual(mock.return_value.foo.mock_calls, - [('', (1, 2, 3), dict(a=4, b=5))]) - self.assertEqual(mock.return_value.mock_calls, - [('foo', (1, 2, 3), dict(a=4, b=5))]) - - mock = MagicMock() - mock().foo.bar().baz() - expected = [ - ('', (), {}), ('().foo.bar', (), {}), - ('().foo.bar().baz', (), {}) - ] - self.assertEqual(mock.mock_calls, expected) - self.assertEqual(mock().mock_calls, - call.foo.bar().baz().call_list()) - - for kwargs in dict(), dict(name='bar'): - mock = MagicMock(**kwargs) - int(mock.foo) - expected = [('foo.__int__', (), {})] - self.assertEqual(mock.mock_calls, expected) - - mock = MagicMock(**kwargs) - mock.a()() - expected = [('a', (), {}), ('a()', (), {})] - self.assertEqual(mock.mock_calls, expected) - self.assertEqual(mock.a().mock_calls, [call()]) - - mock = MagicMock(**kwargs) - mock(1)(2)(3) - self.assertEqual(mock.mock_calls, call(1)(2)(3).call_list()) - self.assertEqual(mock().mock_calls, call(2)(3).call_list()) - self.assertEqual(mock()().mock_calls, call(3).call_list()) - - mock = MagicMock(**kwargs) - mock(1)(2)(3).a.b.c(4) - self.assertEqual(mock.mock_calls, - call(1)(2)(3).a.b.c(4).call_list()) - self.assertEqual(mock().mock_calls, - call(2)(3).a.b.c(4).call_list()) - self.assertEqual(mock()().mock_calls, - call(3).a.b.c(4).call_list()) - - mock = MagicMock(**kwargs) - int(mock().foo.bar().baz()) - last_call = ('().foo.bar().baz().__int__', (), {}) - self.assertEqual(mock.mock_calls[-1], last_call) - self.assertEqual(mock().mock_calls, - call.foo.bar().baz().__int__().call_list()) - self.assertEqual(mock().foo.bar().mock_calls, - call.baz().__int__().call_list()) - self.assertEqual(mock().foo.bar().baz.mock_calls, - call().__int__().call_list()) - - - def test_child_mock_call_equal(self): - m = Mock() - result = m() - result.wibble() - # parent looks like this: - self.assertEqual(m.mock_calls, [call(), call().wibble()]) - # but child should look like this: - self.assertEqual(result.mock_calls, [call.wibble()]) - - - def test_mock_call_not_equal_leaf(self): - m = Mock() - m.foo().something() - self.assertNotEqual(m.mock_calls[1], call.foo().different()) - self.assertEqual(m.mock_calls[0], call.foo()) - - - def test_mock_call_not_equal_non_leaf(self): - m = Mock() - m.foo().bar() - self.assertNotEqual(m.mock_calls[1], call.baz().bar()) - self.assertNotEqual(m.mock_calls[0], call.baz()) - - - def test_mock_call_not_equal_non_leaf_params_different(self): - m = Mock() - m.foo(x=1).bar() - # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: - self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) - - - def test_mock_call_not_equal_non_leaf_attr(self): - m = Mock() - m.foo.bar() - self.assertNotEqual(m.mock_calls[0], call.baz.bar()) - - - def test_mock_call_not_equal_non_leaf_call_versus_attr(self): - m = Mock() - m.foo.bar() - self.assertNotEqual(m.mock_calls[0], call.foo().bar()) - - - def test_mock_call_repr(self): - m = Mock() - m.foo().bar().baz.bob() - self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') - self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') - self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') - - - def test_mock_call_repr_loop(self): - m = Mock() - m.foo = m - repr(m.foo()) - self.assertRegex(repr(m.foo()), r"") - - - def test_mock_calls_contains(self): - m = Mock() - self.assertFalse([call()] in m.mock_calls) - - - def test_subclassing(self): - class Subclass(Mock): - pass - - mock = Subclass() - self.assertIsInstance(mock.foo, Subclass) - self.assertIsInstance(mock(), Subclass) - - class Subclass(Mock): - def _get_child_mock(self, **kwargs): - return Mock(**kwargs) - - mock = Subclass() - self.assertNotIsInstance(mock.foo, Subclass) - self.assertNotIsInstance(mock(), Subclass) - - - def test_arg_lists(self): - mocks = [ - Mock(), - MagicMock(), - NonCallableMock(), - NonCallableMagicMock() - ] - - def assert_attrs(mock): - names = 'call_args_list', 'method_calls', 'mock_calls' - for name in names: - attr = getattr(mock, name) - self.assertIsInstance(attr, _CallList) - self.assertIsInstance(attr, list) - self.assertEqual(attr, []) - - for mock in mocks: - assert_attrs(mock) - - if callable(mock): - mock() - mock(1, 2) - mock(a=3) - - mock.reset_mock() - assert_attrs(mock) - - mock.foo() - mock.foo.bar(1, a=3) - mock.foo(1).bar().baz(3) - - mock.reset_mock() - assert_attrs(mock) - - - def test_call_args_two_tuple(self): - mock = Mock() - mock(1, a=3) - mock(2, b=4) - - self.assertEqual(len(mock.call_args), 2) - self.assertEqual(mock.call_args.args, (2,)) - self.assertEqual(mock.call_args.kwargs, dict(b=4)) - - expected_list = [((1,), dict(a=3)), ((2,), dict(b=4))] - for expected, call_args in zip(expected_list, mock.call_args_list): - self.assertEqual(len(call_args), 2) - self.assertEqual(expected[0], call_args[0]) - self.assertEqual(expected[1], call_args[1]) - - - def test_side_effect_iterator(self): - mock = Mock(side_effect=iter([1, 2, 3])) - self.assertEqual([mock(), mock(), mock()], [1, 2, 3]) - self.assertRaises(StopIteration, mock) - - mock = MagicMock(side_effect=['a', 'b', 'c']) - self.assertEqual([mock(), mock(), mock()], ['a', 'b', 'c']) - self.assertRaises(StopIteration, mock) - - mock = Mock(side_effect='ghi') - self.assertEqual([mock(), mock(), mock()], ['g', 'h', 'i']) - self.assertRaises(StopIteration, mock) - - class Foo(object): - pass - mock = MagicMock(side_effect=Foo) - self.assertIsInstance(mock(), Foo) - - mock = Mock(side_effect=Iter()) - self.assertEqual([mock(), mock(), mock(), mock()], - ['this', 'is', 'an', 'iter']) - self.assertRaises(StopIteration, mock) - - - def test_side_effect_iterator_exceptions(self): - for Klass in Mock, MagicMock: - iterable = (ValueError, 3, KeyError, 6) - m = Klass(side_effect=iterable) - self.assertRaises(ValueError, m) - self.assertEqual(m(), 3) - self.assertRaises(KeyError, m) - self.assertEqual(m(), 6) - - - def test_side_effect_setting_iterator(self): - mock = Mock() - mock.side_effect = iter([1, 2, 3]) - self.assertEqual([mock(), mock(), mock()], [1, 2, 3]) - self.assertRaises(StopIteration, mock) - side_effect = mock.side_effect - self.assertIsInstance(side_effect, type(iter([]))) - - mock.side_effect = ['a', 'b', 'c'] - self.assertEqual([mock(), mock(), mock()], ['a', 'b', 'c']) - self.assertRaises(StopIteration, mock) - side_effect = mock.side_effect - self.assertIsInstance(side_effect, type(iter([]))) - - this_iter = Iter() - mock.side_effect = this_iter - self.assertEqual([mock(), mock(), mock(), mock()], - ['this', 'is', 'an', 'iter']) - self.assertRaises(StopIteration, mock) - self.assertIs(mock.side_effect, this_iter) - - def test_side_effect_iterator_default(self): - mock = Mock(return_value=2) - mock.side_effect = iter([1, DEFAULT]) - self.assertEqual([mock(), mock()], [1, 2]) - - def test_assert_has_calls_any_order(self): - mock = Mock() - mock(1, 2) - mock(a=3) - mock(3, 4) - mock(b=6) - mock(b=6) - - kalls = [ - call(1, 2), ({'a': 3},), - ((3, 4),), ((), {'a': 3}), - ('', (1, 2)), ('', {'a': 3}), - ('', (1, 2), {}), ('', (), {'a': 3}) - ] - for kall in kalls: - mock.assert_has_calls([kall], any_order=True) - - for kall in call(1, '2'), call(b=3), call(), 3, None, 'foo': - self.assertRaises( - AssertionError, mock.assert_has_calls, - [kall], any_order=True - ) - - kall_lists = [ - [call(1, 2), call(b=6)], - [call(3, 4), call(1, 2)], - [call(b=6), call(b=6)], - ] - - for kall_list in kall_lists: - mock.assert_has_calls(kall_list, any_order=True) - - kall_lists = [ - [call(b=6), call(b=6), call(b=6)], - [call(1, 2), call(1, 2)], - [call(3, 4), call(1, 2), call(5, 7)], - [call(b=6), call(3, 4), call(b=6), call(1, 2), call(b=6)], - ] - for kall_list in kall_lists: - self.assertRaises( - AssertionError, mock.assert_has_calls, - kall_list, any_order=True - ) - - def test_assert_has_calls(self): - kalls1 = [ - call(1, 2), ({'a': 3},), - ((3, 4),), call(b=6), - ('', (1,), {'b': 6}), - ] - kalls2 = [call.foo(), call.bar(1)] - kalls2.extend(call.spam().baz(a=3).call_list()) - kalls2.extend(call.bam(set(), foo={}).fish([1]).call_list()) - - mocks = [] - for mock in Mock(), MagicMock(): - mock(1, 2) - mock(a=3) - mock(3, 4) - mock(b=6) - mock(1, b=6) - mocks.append((mock, kalls1)) - - mock = Mock() - mock.foo() - mock.bar(1) - mock.spam().baz(a=3) - mock.bam(set(), foo={}).fish([1]) - mocks.append((mock, kalls2)) - - for mock, kalls in mocks: - for i in range(len(kalls)): - for step in 1, 2, 3: - these = kalls[i:i+step] - mock.assert_has_calls(these) - - if len(these) > 1: - self.assertRaises( - AssertionError, - mock.assert_has_calls, - list(reversed(these)) - ) - - - def test_assert_has_calls_nested_spec(self): - class Something: - - def __init__(self): pass - def meth(self, a, b, c, d=None): pass - - class Foo: - - def __init__(self, a): pass - def meth1(self, a, b): pass - - mock_class = create_autospec(Something) - - for m in [mock_class, mock_class()]: - m.meth(1, 2, 3, d=1) - m.assert_has_calls([call.meth(1, 2, 3, d=1)]) - m.assert_has_calls([call.meth(1, 2, 3, 1)]) - - mock_class.reset_mock() - - for m in [mock_class, mock_class()]: - self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()]) - m.Foo(1).meth1(1, 2) - m.assert_has_calls([call.Foo(1), call.Foo(1).meth1(1, 2)]) - m.Foo.assert_has_calls([call(1), call().meth1(1, 2)]) - - mock_class.reset_mock() - - invalid_calls = [call.meth(1), - call.non_existent(1), - call.Foo().non_existent(1), - call.Foo().meth(1, 2, 3, 4)] - - for kall in invalid_calls: - self.assertRaises(AssertionError, - mock_class.assert_has_calls, - [kall] - ) - - - def test_assert_has_calls_nested_without_spec(self): - m = MagicMock() - m().foo().bar().baz() - m.one().two().three() - calls = call.one().two().three().call_list() - m.assert_has_calls(calls) - - - def test_assert_has_calls_with_function_spec(self): - def f(a, b, c, d=None): pass - - mock = Mock(spec=f) - - mock(1, b=2, c=3) - mock(4, 5, c=6, d=7) - mock(10, 11, c=12) - calls = [ - ('', (1, 2, 3), {}), - ('', (4, 5, 6), {'d': 7}), - ((10, 11, 12), {}), - ] - mock.assert_has_calls(calls) - mock.assert_has_calls(calls, any_order=True) - mock.assert_has_calls(calls[1:]) - mock.assert_has_calls(calls[1:], any_order=True) - mock.assert_has_calls(calls[:-1]) - mock.assert_has_calls(calls[:-1], any_order=True) - # Reversed order - calls = list(reversed(calls)) - with self.assertRaises(AssertionError): - mock.assert_has_calls(calls) - mock.assert_has_calls(calls, any_order=True) - with self.assertRaises(AssertionError): - mock.assert_has_calls(calls[1:]) - mock.assert_has_calls(calls[1:], any_order=True) - with self.assertRaises(AssertionError): - mock.assert_has_calls(calls[:-1]) - mock.assert_has_calls(calls[:-1], any_order=True) - - def test_assert_has_calls_not_matching_spec_error(self): - def f(x=None): pass - - mock = Mock(spec=f) - mock(1) - - with self.assertRaisesRegex( - AssertionError, - '^{}$'.format( - re.escape('Calls not found.\n' - 'Expected: [call()]\n' - 'Actual: [call(1)]'))) as cm: - mock.assert_has_calls([call()]) - self.assertIsNone(cm.exception.__cause__) - - - with self.assertRaisesRegex( - AssertionError, - '^{}$'.format( - re.escape( - 'Error processing expected calls.\n' - "Errors: [None, TypeError('too many positional arguments')]\n" - "Expected: [call(), call(1, 2)]\n" - 'Actual: [call(1)]'))) as cm: - mock.assert_has_calls([call(), call(1, 2)]) - self.assertIsInstance(cm.exception.__cause__, TypeError) - - def test_assert_any_call(self): - mock = Mock() - mock(1, 2) - mock(a=3) - mock(1, b=6) - - mock.assert_any_call(1, 2) - mock.assert_any_call(a=3) - mock.assert_any_call(1, b=6) - - self.assertRaises( - AssertionError, - mock.assert_any_call - ) - self.assertRaises( - AssertionError, - mock.assert_any_call, - 1, 3 - ) - self.assertRaises( - AssertionError, - mock.assert_any_call, - a=4 - ) - - - def test_assert_any_call_with_function_spec(self): - def f(a, b, c, d=None): pass - - mock = Mock(spec=f) - - mock(1, b=2, c=3) - mock(4, 5, c=6, d=7) - mock.assert_any_call(1, 2, 3) - mock.assert_any_call(a=1, b=2, c=3) - mock.assert_any_call(4, 5, 6, 7) - mock.assert_any_call(a=4, b=5, c=6, d=7) - self.assertRaises(AssertionError, mock.assert_any_call, - 1, b=3, c=2) - # Expected call doesn't match the spec's signature - with self.assertRaises(AssertionError) as cm: - mock.assert_any_call(e=8) - self.assertIsInstance(cm.exception.__cause__, TypeError) - - - def test_mock_calls_create_autospec(self): - def f(a, b): pass - obj = Iter() - obj.f = f - - funcs = [ - create_autospec(f), - create_autospec(obj).f - ] - for func in funcs: - func(1, 2) - func(3, 4) - - self.assertEqual( - func.mock_calls, [call(1, 2), call(3, 4)] - ) - - #Issue21222 - def test_create_autospec_with_name(self): - m = mock.create_autospec(object(), name='sweet_func') - self.assertIn('sweet_func', repr(m)) - - #Issue23078 - def test_create_autospec_classmethod_and_staticmethod(self): - class TestClass: - @classmethod - def class_method(cls): pass - - @staticmethod - def static_method(): pass - for method in ('class_method', 'static_method'): - with self.subTest(method=method): - mock_method = mock.create_autospec(getattr(TestClass, method)) - mock_method() - mock_method.assert_called_once_with() - self.assertRaises(TypeError, mock_method, 'extra_arg') - - #Issue21238 - def test_mock_unsafe(self): - m = Mock() - msg = "Attributes cannot start with 'assert' or 'assret'" - with self.assertRaisesRegex(AttributeError, msg): - m.assert_foo_call() - with self.assertRaisesRegex(AttributeError, msg): - m.assret_foo_call() - m = Mock(unsafe=True) - m.assert_foo_call() - m.assret_foo_call() - - #Issue21262 - def test_assert_not_called(self): - m = Mock() - m.hello.assert_not_called() - m.hello() - with self.assertRaises(AssertionError): - m.hello.assert_not_called() - - def test_assert_not_called_message(self): - m = Mock() - m(1, 2) - self.assertRaisesRegex(AssertionError, - re.escape("Calls: [call(1, 2)]"), - m.assert_not_called) - - def test_assert_called(self): - m = Mock() - with self.assertRaises(AssertionError): - m.hello.assert_called() - m.hello() - m.hello.assert_called() - - m.hello() - m.hello.assert_called() - - def test_assert_called_once(self): - m = Mock() - with self.assertRaises(AssertionError): - m.hello.assert_called_once() - m.hello() - m.hello.assert_called_once() - - m.hello() - with self.assertRaises(AssertionError): - m.hello.assert_called_once() - - def test_assert_called_once_message(self): - m = Mock() - m(1, 2) - m(3) - self.assertRaisesRegex(AssertionError, - re.escape("Calls: [call(1, 2), call(3)]"), - m.assert_called_once) - - def test_assert_called_once_message_not_called(self): - m = Mock() - with self.assertRaises(AssertionError) as e: - m.assert_called_once() - self.assertNotIn("Calls:", str(e.exception)) - - #Issue37212 printout of keyword args now preserves the original order - def test_ordered_call_signature(self): - m = Mock() - m.hello(name='hello', daddy='hero') - text = "call(name='hello', daddy='hero')" - self.assertEqual(repr(m.hello.call_args), text) - - #Issue21270 overrides tuple methods for mock.call objects - def test_override_tuple_methods(self): - c = call.count() - i = call.index(132,'hello') - m = Mock() - m.count() - m.index(132,"hello") - self.assertEqual(m.method_calls[0], c) - self.assertEqual(m.method_calls[1], i) - - def test_reset_return_sideeffect(self): - m = Mock(return_value=10, side_effect=[2,3]) - m.reset_mock(return_value=True, side_effect=True) - self.assertIsInstance(m.return_value, Mock) - self.assertEqual(m.side_effect, None) - - def test_reset_return(self): - m = Mock(return_value=10, side_effect=[2,3]) - m.reset_mock(return_value=True) - self.assertIsInstance(m.return_value, Mock) - self.assertNotEqual(m.side_effect, None) - - def test_reset_sideeffect(self): - m = Mock(return_value=10, side_effect=[2, 3]) - m.reset_mock(side_effect=True) - self.assertEqual(m.return_value, 10) - self.assertEqual(m.side_effect, None) - - def test_reset_return_with_children(self): - m = MagicMock(f=MagicMock(return_value=1)) - self.assertEqual(m.f(), 1) - m.reset_mock(return_value=True) - self.assertNotEqual(m.f(), 1) - - def test_reset_return_with_children_side_effect(self): - m = MagicMock(f=MagicMock(side_effect=[2, 3])) - self.assertNotEqual(m.f.side_effect, None) - m.reset_mock(side_effect=True) - self.assertEqual(m.f.side_effect, None) - - def test_mock_add_spec(self): - class _One(object): - one = 1 - class _Two(object): - two = 2 - class Anything(object): - one = two = three = 'four' - - klasses = [ - Mock, MagicMock, NonCallableMock, NonCallableMagicMock - ] - for Klass in list(klasses): - klasses.append(lambda K=Klass: K(spec=Anything)) - klasses.append(lambda K=Klass: K(spec_set=Anything)) - - for Klass in klasses: - for kwargs in dict(), dict(spec_set=True): - mock = Klass() - #no error - mock.one, mock.two, mock.three - - for One, Two in [(_One, _Two), (['one'], ['two'])]: - for kwargs in dict(), dict(spec_set=True): - mock.mock_add_spec(One, **kwargs) - - mock.one - self.assertRaises( - AttributeError, getattr, mock, 'two' - ) - self.assertRaises( - AttributeError, getattr, mock, 'three' - ) - if 'spec_set' in kwargs: - self.assertRaises( - AttributeError, setattr, mock, 'three', None - ) - - mock.mock_add_spec(Two, **kwargs) - self.assertRaises( - AttributeError, getattr, mock, 'one' - ) - mock.two - self.assertRaises( - AttributeError, getattr, mock, 'three' - ) - if 'spec_set' in kwargs: - self.assertRaises( - AttributeError, setattr, mock, 'three', None - ) - # note that creating a mock, setting an instance attribute, and - # *then* setting a spec doesn't work. Not the intended use case - - - def test_mock_add_spec_magic_methods(self): - for Klass in MagicMock, NonCallableMagicMock: - mock = Klass() - int(mock) - - mock.mock_add_spec(object) - self.assertRaises(TypeError, int, mock) - - mock = Klass() - mock['foo'] - mock.__int__.return_value =4 - - mock.mock_add_spec(int) - self.assertEqual(int(mock), 4) - self.assertRaises(TypeError, lambda: mock['foo']) - - - def test_adding_child_mock(self): - for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, - AsyncMock): - mock = Klass() - - mock.foo = Mock() - mock.foo() - - self.assertEqual(mock.method_calls, [call.foo()]) - self.assertEqual(mock.mock_calls, [call.foo()]) - - mock = Klass() - mock.bar = Mock(name='name') - mock.bar() - self.assertEqual(mock.method_calls, []) - self.assertEqual(mock.mock_calls, []) - - # mock with an existing _new_parent but no name - mock = Klass() - mock.baz = MagicMock()() - mock.baz() - self.assertEqual(mock.method_calls, []) - self.assertEqual(mock.mock_calls, []) - - - def test_adding_return_value_mock(self): - for Klass in Mock, MagicMock: - mock = Klass() - mock.return_value = MagicMock() - - mock()() - self.assertEqual(mock.mock_calls, [call(), call()()]) - - - def test_manager_mock(self): - class Foo(object): - one = 'one' - two = 'two' - manager = Mock() - p1 = patch.object(Foo, 'one') - p2 = patch.object(Foo, 'two') - - mock_one = p1.start() - self.addCleanup(p1.stop) - mock_two = p2.start() - self.addCleanup(p2.stop) - - manager.attach_mock(mock_one, 'one') - manager.attach_mock(mock_two, 'two') - - Foo.two() - Foo.one() - - self.assertEqual(manager.mock_calls, [call.two(), call.one()]) - - - def test_magic_methods_mock_calls(self): - for Klass in Mock, MagicMock: - m = Klass() - m.__int__ = Mock(return_value=3) - m.__float__ = MagicMock(return_value=3.0) - int(m) - float(m) - - self.assertEqual(m.mock_calls, [call.__int__(), call.__float__()]) - self.assertEqual(m.method_calls, []) - - def test_mock_open_reuse_issue_21750(self): - mocked_open = mock.mock_open(read_data='data') - f1 = mocked_open('a-name') - f1_data = f1.read() - f2 = mocked_open('another-name') - f2_data = f2.read() - self.assertEqual(f1_data, f2_data) - - def test_mock_open_dunder_iter_issue(self): - # Test dunder_iter method generates the expected result and - # consumes the iterator. - mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') - f1 = mocked_open('a-name') - lines = [line for line in f1] - self.assertEqual(lines[0], 'Remarkable\n') - self.assertEqual(lines[1], 'Norwegian Blue') - self.assertEqual(list(f1), []) - - def test_mock_open_using_next(self): - mocked_open = mock.mock_open(read_data='1st line\n2nd line\n3rd line') - f1 = mocked_open('a-name') - line1 = next(f1) - line2 = f1.__next__() - lines = [line for line in f1] - self.assertEqual(line1, '1st line\n') - self.assertEqual(line2, '2nd line\n') - self.assertEqual(lines[0], '3rd line') - self.assertEqual(list(f1), []) - with self.assertRaises(StopIteration): - next(f1) - - def test_mock_open_next_with_readline_with_return_value(self): - mopen = mock.mock_open(read_data='foo\nbarn') - mopen.return_value.readline.return_value = 'abc' - self.assertEqual('abc', next(mopen())) - - def test_mock_open_write(self): - # Test exception in file writing write() - mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) - with mock.patch('tempfile.NamedTemporaryFile', mock_namedtemp): - mock_filehandle = mock_namedtemp.return_value - mock_write = mock_filehandle.write - mock_write.side_effect = OSError('Test 2 Error') - def attempt(): - tempfile.NamedTemporaryFile().write('asd') - self.assertRaises(OSError, attempt) - - def test_mock_open_alter_readline(self): - mopen = mock.mock_open(read_data='foo\nbarn') - mopen.return_value.readline.side_effect = lambda *args:'abc' - first = mopen().readline() - second = mopen().readline() - self.assertEqual('abc', first) - self.assertEqual('abc', second) - - def test_mock_open_after_eof(self): - # read, readline and readlines should work after end of file. - _open = mock.mock_open(read_data='foo') - h = _open('bar') - h.read() - self.assertEqual('', h.read()) - self.assertEqual('', h.read()) - self.assertEqual('', h.readline()) - self.assertEqual('', h.readline()) - self.assertEqual([], h.readlines()) - self.assertEqual([], h.readlines()) - - def test_mock_parents(self): - for Klass in Mock, MagicMock: - m = Klass() - original_repr = repr(m) - m.return_value = m - self.assertIs(m(), m) - self.assertEqual(repr(m), original_repr) - - m.reset_mock() - self.assertIs(m(), m) - self.assertEqual(repr(m), original_repr) - - m = Klass() - m.b = m.a - self.assertIn("name='mock.a'", repr(m.b)) - self.assertIn("name='mock.a'", repr(m.a)) - m.reset_mock() - self.assertIn("name='mock.a'", repr(m.b)) - self.assertIn("name='mock.a'", repr(m.a)) - - m = Klass() - original_repr = repr(m) - m.a = m() - m.a.return_value = m - - self.assertEqual(repr(m), original_repr) - self.assertEqual(repr(m.a()), original_repr) - - - def test_attach_mock(self): - classes = Mock, MagicMock, NonCallableMagicMock, NonCallableMock - for Klass in classes: - for Klass2 in classes: - m = Klass() - - m2 = Klass2(name='foo') - m.attach_mock(m2, 'bar') - - self.assertIs(m.bar, m2) - self.assertIn("name='mock.bar'", repr(m2)) - - m.bar.baz(1) - self.assertEqual(m.mock_calls, [call.bar.baz(1)]) - self.assertEqual(m.method_calls, [call.bar.baz(1)]) - - - def test_attach_mock_return_value(self): - classes = Mock, MagicMock, NonCallableMagicMock, NonCallableMock - for Klass in Mock, MagicMock: - for Klass2 in classes: - m = Klass() - - m2 = Klass2(name='foo') - m.attach_mock(m2, 'return_value') - - self.assertIs(m(), m2) - self.assertIn("name='mock()'", repr(m2)) - - m2.foo() - self.assertEqual(m.mock_calls, call().foo().call_list()) - - - def test_attach_mock_patch_autospec(self): - parent = Mock() - - with mock.patch(f'{__name__}.something', autospec=True) as mock_func: - self.assertEqual(mock_func.mock._extract_mock_name(), 'something') - parent.attach_mock(mock_func, 'child') - parent.child(1) - something(2) - mock_func(3) - - parent_calls = [call.child(1), call.child(2), call.child(3)] - child_calls = [call(1), call(2), call(3)] - self.assertEqual(parent.mock_calls, parent_calls) - self.assertEqual(parent.child.mock_calls, child_calls) - self.assertEqual(something.mock_calls, child_calls) - self.assertEqual(mock_func.mock_calls, child_calls) - self.assertIn('mock.child', repr(parent.child.mock)) - self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child') - - - def test_attach_mock_patch_autospec_signature(self): - with mock.patch(f'{__name__}.Something.meth', autospec=True) as mocked: - manager = Mock() - manager.attach_mock(mocked, 'attach_meth') - obj = Something() - obj.meth(1, 2, 3, d=4) - manager.assert_has_calls([call.attach_meth(mock.ANY, 1, 2, 3, d=4)]) - obj.meth.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) - mocked.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) - - with mock.patch(f'{__name__}.something', autospec=True) as mocked: - manager = Mock() - manager.attach_mock(mocked, 'attach_func') - something(1) - manager.assert_has_calls([call.attach_func(1)]) - something.assert_has_calls([call(1)]) - mocked.assert_has_calls([call(1)]) - - with mock.patch(f'{__name__}.Something', autospec=True) as mocked: - manager = Mock() - manager.attach_mock(mocked, 'attach_obj') - obj = Something() - obj.meth(1, 2, 3, d=4) - manager.assert_has_calls([call.attach_obj(), - call.attach_obj().meth(1, 2, 3, d=4)]) - obj.meth.assert_has_calls([call(1, 2, 3, d=4)]) - mocked.assert_has_calls([call(), call().meth(1, 2, 3, d=4)]) - - - def test_attribute_deletion(self): - for mock in (Mock(), MagicMock(), NonCallableMagicMock(), - NonCallableMock()): - self.assertTrue(hasattr(mock, 'm')) - - del mock.m - self.assertFalse(hasattr(mock, 'm')) - - del mock.f - self.assertFalse(hasattr(mock, 'f')) - self.assertRaises(AttributeError, getattr, mock, 'f') - - - def test_mock_does_not_raise_on_repeated_attribute_deletion(self): - # bpo-20239: Assigning and deleting twice an attribute raises. - for mock in (Mock(), MagicMock(), NonCallableMagicMock(), - NonCallableMock()): - mock.foo = 3 - self.assertTrue(hasattr(mock, 'foo')) - self.assertEqual(mock.foo, 3) - - del mock.foo - self.assertFalse(hasattr(mock, 'foo')) - - mock.foo = 4 - self.assertTrue(hasattr(mock, 'foo')) - self.assertEqual(mock.foo, 4) - - del mock.foo - self.assertFalse(hasattr(mock, 'foo')) - - - def test_mock_raises_when_deleting_nonexistent_attribute(self): - for mock in (Mock(), MagicMock(), NonCallableMagicMock(), - NonCallableMock()): - del mock.foo - with self.assertRaises(AttributeError): - del mock.foo - - - def test_reset_mock_does_not_raise_on_attr_deletion(self): - # bpo-31177: reset_mock should not raise AttributeError when attributes - # were deleted in a mock instance - mock = Mock() - mock.child = True - del mock.child - mock.reset_mock() - self.assertFalse(hasattr(mock, 'child')) - - - def test_class_assignable(self): - for mock in Mock(), MagicMock(): - self.assertNotIsInstance(mock, int) - - mock.__class__ = int - self.assertIsInstance(mock, int) - mock.foo - - def test_name_attribute_of_call(self): - # bpo-35357: _Call should not disclose any attributes whose names - # may clash with popular ones (such as ".name") - self.assertIsNotNone(call.name) - self.assertEqual(type(call.name), _Call) - self.assertEqual(type(call.name().name), _Call) - - def test_parent_attribute_of_call(self): - # bpo-35357: _Call should not disclose any attributes whose names - # may clash with popular ones (such as ".parent") - self.assertIsNotNone(call.parent) - self.assertEqual(type(call.parent), _Call) - self.assertEqual(type(call.parent().parent), _Call) - - - def test_parent_propagation_with_create_autospec(self): - - def foo(a, b): pass - - mock = Mock() - mock.child = create_autospec(foo) - mock.child(1, 2) - - self.assertRaises(TypeError, mock.child, 1) - self.assertEqual(mock.mock_calls, [call.child(1, 2)]) - self.assertIn('mock.child', repr(mock.child.mock)) - - def test_parent_propagation_with_autospec_attach_mock(self): - - def foo(a, b): pass - - parent = Mock() - parent.attach_mock(create_autospec(foo, name='bar'), 'child') - parent.child(1, 2) - - self.assertRaises(TypeError, parent.child, 1) - self.assertEqual(parent.child.mock_calls, [call.child(1, 2)]) - self.assertIn('mock.child', repr(parent.child.mock)) - - - def test_isinstance_under_settrace(self): - # bpo-36593 : __class__ is not set for a class that has __class__ - # property defined when it's used with sys.settrace(trace) set. - # Delete the module to force reimport with tracing function set - # restore the old reference later since there are other tests that are - # dependent on unittest.mock.patch. In testpatch.PatchTest - # test_patch_dict_test_prefix and test_patch_test_prefix not restoring - # causes the objects patched to go out of sync - - old_patch = unittest.mock.patch - - # Directly using __setattr__ on unittest.mock causes current imported - # reference to be updated. Use a lambda so that during cleanup the - # re-imported new reference is updated. - self.addCleanup(lambda patch: setattr(unittest.mock, 'patch', patch), - old_patch) - - with patch.dict('sys.modules'): - del sys.modules['unittest.mock'] - - # This trace will stop coverage being measured ;-) - def trace(frame, event, arg): # pragma: no cover - return trace - - self.addCleanup(sys.settrace, sys.gettrace()) - sys.settrace(trace) - - from unittest.mock import ( - Mock, MagicMock, NonCallableMock, NonCallableMagicMock - ) - - mocks = [ - Mock, MagicMock, NonCallableMock, NonCallableMagicMock, AsyncMock - ] - - for mock in mocks: - obj = mock(spec=Something) - self.assertIsInstance(obj, Something) - - def test_bool_not_called_when_passing_spec_arg(self): - class Something: - def __init__(self): - self.obj_with_bool_func = unittest.mock.MagicMock() - - obj = Something() - with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass - - self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py b/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py deleted file mode 100644 index d8c1515f83..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testpatch.py +++ /dev/null @@ -1,1947 +0,0 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -import os -import sys -from collections import OrderedDict - -import unittest -from unittest.test.testmock import support -from unittest.test.testmock.support import SomeClass, is_instance - -from test.test_importlib.util import uncache -from unittest.mock import ( - NonCallableMock, CallableMixin, sentinel, - MagicMock, Mock, NonCallableMagicMock, patch, _patch, - DEFAULT, call, _get_target -) - - -builtin_string = 'builtins' - -PTModule = sys.modules[__name__] -MODNAME = '%s.PTModule' % __name__ - - -def _get_proxy(obj, get_only=True): - class Proxy(object): - def __getattr__(self, name): - return getattr(obj, name) - if not get_only: - def __setattr__(self, name, value): - setattr(obj, name, value) - def __delattr__(self, name): - delattr(obj, name) - Proxy.__setattr__ = __setattr__ - Proxy.__delattr__ = __delattr__ - return Proxy() - - -# for use in the test -something = sentinel.Something -something_else = sentinel.SomethingElse - - -class Foo(object): - def __init__(self, a): pass - def f(self, a): pass - def g(self): pass - foo = 'bar' - - @staticmethod - def static_method(): pass - - @classmethod - def class_method(cls): pass - - class Bar(object): - def a(self): pass - -foo_name = '%s.Foo' % __name__ - - -def function(a, b=Foo): pass - - -class Container(object): - def __init__(self): - self.values = {} - - def __getitem__(self, name): - return self.values[name] - - def __setitem__(self, name, value): - self.values[name] = value - - def __delitem__(self, name): - del self.values[name] - - def __iter__(self): - return iter(self.values) - - - -class PatchTest(unittest.TestCase): - - def assertNotCallable(self, obj, magic=True): - MockClass = NonCallableMagicMock - if not magic: - MockClass = NonCallableMock - - self.assertRaises(TypeError, obj) - self.assertTrue(is_instance(obj, MockClass)) - self.assertFalse(is_instance(obj, CallableMixin)) - - - def test_single_patchobject(self): - class Something(object): - attribute = sentinel.Original - - @patch.object(Something, 'attribute', sentinel.Patched) - def test(): - self.assertEqual(Something.attribute, sentinel.Patched, "unpatched") - - test() - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - - def test_patchobject_with_string_as_target(self): - msg = "'Something' must be the actual object to be patched, not a str" - with self.assertRaisesRegex(TypeError, msg): - patch.object('Something', 'do_something') - - def test_patchobject_with_none(self): - class Something(object): - attribute = sentinel.Original - - @patch.object(Something, 'attribute', None) - def test(): - self.assertIsNone(Something.attribute, "unpatched") - - test() - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - - - def test_multiple_patchobject(self): - class Something(object): - attribute = sentinel.Original - next_attribute = sentinel.Original2 - - @patch.object(Something, 'attribute', sentinel.Patched) - @patch.object(Something, 'next_attribute', sentinel.Patched2) - def test(): - self.assertEqual(Something.attribute, sentinel.Patched, - "unpatched") - self.assertEqual(Something.next_attribute, sentinel.Patched2, - "unpatched") - - test() - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - self.assertEqual(Something.next_attribute, sentinel.Original2, - "patch not restored") - - - def test_object_lookup_is_quite_lazy(self): - global something - original = something - @patch('%s.something' % __name__, sentinel.Something2) - def test(): - pass - - try: - something = sentinel.replacement_value - test() - self.assertEqual(something, sentinel.replacement_value) - finally: - something = original - - - def test_patch(self): - @patch('%s.something' % __name__, sentinel.Something2) - def test(): - self.assertEqual(PTModule.something, sentinel.Something2, - "unpatched") - - test() - self.assertEqual(PTModule.something, sentinel.Something, - "patch not restored") - - @patch('%s.something' % __name__, sentinel.Something2) - @patch('%s.something_else' % __name__, sentinel.SomethingElse) - def test(): - self.assertEqual(PTModule.something, sentinel.Something2, - "unpatched") - self.assertEqual(PTModule.something_else, sentinel.SomethingElse, - "unpatched") - - self.assertEqual(PTModule.something, sentinel.Something, - "patch not restored") - self.assertEqual(PTModule.something_else, sentinel.SomethingElse, - "patch not restored") - - # Test the patching and restoring works a second time - test() - - self.assertEqual(PTModule.something, sentinel.Something, - "patch not restored") - self.assertEqual(PTModule.something_else, sentinel.SomethingElse, - "patch not restored") - - mock = Mock() - mock.return_value = sentinel.Handle - @patch('%s.open' % builtin_string, mock) - def test(): - self.assertEqual(open('filename', 'r'), sentinel.Handle, - "open not patched") - test() - test() - - self.assertNotEqual(open, mock, "patch not restored") - - - def test_patch_class_attribute(self): - @patch('%s.SomeClass.class_attribute' % __name__, - sentinel.ClassAttribute) - def test(): - self.assertEqual(PTModule.SomeClass.class_attribute, - sentinel.ClassAttribute, "unpatched") - test() - - self.assertIsNone(PTModule.SomeClass.class_attribute, - "patch not restored") - - - def test_patchobject_with_default_mock(self): - class Test(object): - something = sentinel.Original - something2 = sentinel.Original2 - - @patch.object(Test, 'something') - def test(mock): - self.assertEqual(mock, Test.something, - "Mock not passed into test function") - self.assertIsInstance(mock, MagicMock, - "patch with two arguments did not create a mock") - - test() - - @patch.object(Test, 'something') - @patch.object(Test, 'something2') - def test(this1, this2, mock1, mock2): - self.assertEqual(this1, sentinel.this1, - "Patched function didn't receive initial argument") - self.assertEqual(this2, sentinel.this2, - "Patched function didn't receive second argument") - self.assertEqual(mock1, Test.something2, - "Mock not passed into test function") - self.assertEqual(mock2, Test.something, - "Second Mock not passed into test function") - self.assertIsInstance(mock2, MagicMock, - "patch with two arguments did not create a mock") - self.assertIsInstance(mock2, MagicMock, - "patch with two arguments did not create a mock") - - # A hack to test that new mocks are passed the second time - self.assertNotEqual(outerMock1, mock1, "unexpected value for mock1") - self.assertNotEqual(outerMock2, mock2, "unexpected value for mock1") - return mock1, mock2 - - outerMock1 = outerMock2 = None - outerMock1, outerMock2 = test(sentinel.this1, sentinel.this2) - - # Test that executing a second time creates new mocks - test(sentinel.this1, sentinel.this2) - - - def test_patch_with_spec(self): - @patch('%s.SomeClass' % __name__, spec=SomeClass) - def test(MockSomeClass): - self.assertEqual(SomeClass, MockSomeClass) - self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) - self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) - - test() - - - def test_patchobject_with_spec(self): - @patch.object(SomeClass, 'class_attribute', spec=SomeClass) - def test(MockAttribute): - self.assertEqual(SomeClass.class_attribute, MockAttribute) - self.assertTrue(is_instance(SomeClass.class_attribute.wibble, - MagicMock)) - self.assertRaises(AttributeError, - lambda: SomeClass.class_attribute.not_wibble) - - test() - - - def test_patch_with_spec_as_list(self): - @patch('%s.SomeClass' % __name__, spec=['wibble']) - def test(MockSomeClass): - self.assertEqual(SomeClass, MockSomeClass) - self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) - self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) - - test() - - - def test_patchobject_with_spec_as_list(self): - @patch.object(SomeClass, 'class_attribute', spec=['wibble']) - def test(MockAttribute): - self.assertEqual(SomeClass.class_attribute, MockAttribute) - self.assertTrue(is_instance(SomeClass.class_attribute.wibble, - MagicMock)) - self.assertRaises(AttributeError, - lambda: SomeClass.class_attribute.not_wibble) - - test() - - - def test_nested_patch_with_spec_as_list(self): - # regression test for nested decorators - @patch('%s.open' % builtin_string) - @patch('%s.SomeClass' % __name__, spec=['wibble']) - def test(MockSomeClass, MockOpen): - self.assertEqual(SomeClass, MockSomeClass) - self.assertTrue(is_instance(SomeClass.wibble, MagicMock)) - self.assertRaises(AttributeError, lambda: SomeClass.not_wibble) - test() - - - def test_patch_with_spec_as_boolean(self): - @patch('%s.SomeClass' % __name__, spec=True) - def test(MockSomeClass): - self.assertEqual(SomeClass, MockSomeClass) - # Should not raise attribute error - MockSomeClass.wibble - - self.assertRaises(AttributeError, lambda: MockSomeClass.not_wibble) - - test() - - - def test_patch_object_with_spec_as_boolean(self): - @patch.object(PTModule, 'SomeClass', spec=True) - def test(MockSomeClass): - self.assertEqual(SomeClass, MockSomeClass) - # Should not raise attribute error - MockSomeClass.wibble - - self.assertRaises(AttributeError, lambda: MockSomeClass.not_wibble) - - test() - - - def test_patch_class_acts_with_spec_is_inherited(self): - @patch('%s.SomeClass' % __name__, spec=True) - def test(MockSomeClass): - self.assertTrue(is_instance(MockSomeClass, MagicMock)) - instance = MockSomeClass() - self.assertNotCallable(instance) - # Should not raise attribute error - instance.wibble - - self.assertRaises(AttributeError, lambda: instance.not_wibble) - - test() - - - def test_patch_with_create_mocks_non_existent_attributes(self): - @patch('%s.frooble' % builtin_string, sentinel.Frooble, create=True) - def test(): - self.assertEqual(frooble, sentinel.Frooble) - - test() - self.assertRaises(NameError, lambda: frooble) - - - def test_patchobject_with_create_mocks_non_existent_attributes(self): - @patch.object(SomeClass, 'frooble', sentinel.Frooble, create=True) - def test(): - self.assertEqual(SomeClass.frooble, sentinel.Frooble) - - test() - self.assertFalse(hasattr(SomeClass, 'frooble')) - - - def test_patch_wont_create_by_default(self): - with self.assertRaises(AttributeError): - @patch('%s.frooble' % builtin_string, sentinel.Frooble) - def test(): pass - - test() - self.assertRaises(NameError, lambda: frooble) - - - def test_patchobject_wont_create_by_default(self): - with self.assertRaises(AttributeError): - @patch.object(SomeClass, 'ord', sentinel.Frooble) - def test(): pass - test() - self.assertFalse(hasattr(SomeClass, 'ord')) - - - def test_patch_builtins_without_create(self): - @patch(__name__+'.ord') - def test_ord(mock_ord): - mock_ord.return_value = 101 - return ord('c') - - @patch(__name__+'.open') - def test_open(mock_open): - m = mock_open.return_value - m.read.return_value = 'abcd' - - fobj = open('doesnotexists.txt') - data = fobj.read() - fobj.close() - return data - - self.assertEqual(test_ord(), 101) - self.assertEqual(test_open(), 'abcd') - - - def test_patch_with_static_methods(self): - class Foo(object): - @staticmethod - def woot(): - return sentinel.Static - - @patch.object(Foo, 'woot', staticmethod(lambda: sentinel.Patched)) - def anonymous(): - self.assertEqual(Foo.woot(), sentinel.Patched) - anonymous() - - self.assertEqual(Foo.woot(), sentinel.Static) - - - def test_patch_local(self): - foo = sentinel.Foo - @patch.object(sentinel, 'Foo', 'Foo') - def anonymous(): - self.assertEqual(sentinel.Foo, 'Foo') - anonymous() - - self.assertEqual(sentinel.Foo, foo) - - - def test_patch_slots(self): - class Foo(object): - __slots__ = ('Foo',) - - foo = Foo() - foo.Foo = sentinel.Foo - - @patch.object(foo, 'Foo', 'Foo') - def anonymous(): - self.assertEqual(foo.Foo, 'Foo') - anonymous() - - self.assertEqual(foo.Foo, sentinel.Foo) - - - def test_patchobject_class_decorator(self): - class Something(object): - attribute = sentinel.Original - - class Foo(object): - def test_method(other_self): - self.assertEqual(Something.attribute, sentinel.Patched, - "unpatched") - def not_test_method(other_self): - self.assertEqual(Something.attribute, sentinel.Original, - "non-test method patched") - - Foo = patch.object(Something, 'attribute', sentinel.Patched)(Foo) - - f = Foo() - f.test_method() - f.not_test_method() - - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - - - def test_patch_class_decorator(self): - class Something(object): - attribute = sentinel.Original - - class Foo(object): - - test_class_attr = 'whatever' - - def test_method(other_self, mock_something): - self.assertEqual(PTModule.something, mock_something, - "unpatched") - def not_test_method(other_self): - self.assertEqual(PTModule.something, sentinel.Something, - "non-test method patched") - Foo = patch('%s.something' % __name__)(Foo) - - f = Foo() - f.test_method() - f.not_test_method() - - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - self.assertEqual(PTModule.something, sentinel.Something, - "patch not restored") - - - def test_patchobject_twice(self): - class Something(object): - attribute = sentinel.Original - next_attribute = sentinel.Original2 - - @patch.object(Something, 'attribute', sentinel.Patched) - @patch.object(Something, 'attribute', sentinel.Patched) - def test(): - self.assertEqual(Something.attribute, sentinel.Patched, "unpatched") - - test() - - self.assertEqual(Something.attribute, sentinel.Original, - "patch not restored") - - - def test_patch_dict(self): - foo = {'initial': object(), 'other': 'something'} - original = foo.copy() - - @patch.dict(foo) - def test(): - foo['a'] = 3 - del foo['initial'] - foo['other'] = 'something else' - - test() - - self.assertEqual(foo, original) - - @patch.dict(foo, {'a': 'b'}) - def test(): - self.assertEqual(len(foo), 3) - self.assertEqual(foo['a'], 'b') - - test() - - self.assertEqual(foo, original) - - @patch.dict(foo, [('a', 'b')]) - def test(): - self.assertEqual(len(foo), 3) - self.assertEqual(foo['a'], 'b') - - test() - - self.assertEqual(foo, original) - - - def test_patch_dict_with_container_object(self): - foo = Container() - foo['initial'] = object() - foo['other'] = 'something' - - original = foo.values.copy() - - @patch.dict(foo) - def test(): - foo['a'] = 3 - del foo['initial'] - foo['other'] = 'something else' - - test() - - self.assertEqual(foo.values, original) - - @patch.dict(foo, {'a': 'b'}) - def test(): - self.assertEqual(len(foo.values), 3) - self.assertEqual(foo['a'], 'b') - - test() - - self.assertEqual(foo.values, original) - - - def test_patch_dict_with_clear(self): - foo = {'initial': object(), 'other': 'something'} - original = foo.copy() - - @patch.dict(foo, clear=True) - def test(): - self.assertEqual(foo, {}) - foo['a'] = 3 - foo['other'] = 'something else' - - test() - - self.assertEqual(foo, original) - - @patch.dict(foo, {'a': 'b'}, clear=True) - def test(): - self.assertEqual(foo, {'a': 'b'}) - - test() - - self.assertEqual(foo, original) - - @patch.dict(foo, [('a', 'b')], clear=True) - def test(): - self.assertEqual(foo, {'a': 'b'}) - - test() - - self.assertEqual(foo, original) - - - def test_patch_dict_with_container_object_and_clear(self): - foo = Container() - foo['initial'] = object() - foo['other'] = 'something' - - original = foo.values.copy() - - @patch.dict(foo, clear=True) - def test(): - self.assertEqual(foo.values, {}) - foo['a'] = 3 - foo['other'] = 'something else' - - test() - - self.assertEqual(foo.values, original) - - @patch.dict(foo, {'a': 'b'}, clear=True) - def test(): - self.assertEqual(foo.values, {'a': 'b'}) - - test() - - self.assertEqual(foo.values, original) - - - def test_patch_dict_as_context_manager(self): - foo = {'a': 'b'} - with patch.dict(foo, a='c') as patched: - self.assertEqual(patched, {'a': 'c'}) - self.assertEqual(foo, {'a': 'b'}) - - - def test_name_preserved(self): - foo = {} - - @patch('%s.SomeClass' % __name__, object()) - @patch('%s.SomeClass' % __name__, object(), autospec=True) - @patch.object(SomeClass, object()) - @patch.dict(foo) - def some_name(): pass - - self.assertEqual(some_name.__name__, 'some_name') - - - def test_patch_with_exception(self): - foo = {} - - @patch.dict(foo, {'a': 'b'}) - def test(): - raise NameError('Konrad') - - with self.assertRaises(NameError): - test() - - self.assertEqual(foo, {}) - - - def test_patch_dict_with_string(self): - @patch.dict('os.environ', {'konrad_delong': 'some value'}) - def test(): - self.assertIn('konrad_delong', os.environ) - - test() - - - def test_patch_dict_decorator_resolution(self): - # bpo-35512: Ensure that patch with a string target resolves to - # the new dictionary during function call - original = support.target.copy() - - @patch.dict('unittest.test.testmock.support.target', {'bar': 'BAR'}) - def test(): - self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) - - try: - support.target = {'foo': 'BAZ'} - test() - self.assertEqual(support.target, {'foo': 'BAZ'}) - finally: - support.target = original - - - def test_patch_spec_set(self): - @patch('%s.SomeClass' % __name__, spec=SomeClass, spec_set=True) - def test(MockClass): - MockClass.z = 'foo' - - self.assertRaises(AttributeError, test) - - @patch.object(support, 'SomeClass', spec=SomeClass, spec_set=True) - def test(MockClass): - MockClass.z = 'foo' - - self.assertRaises(AttributeError, test) - @patch('%s.SomeClass' % __name__, spec_set=True) - def test(MockClass): - MockClass.z = 'foo' - - self.assertRaises(AttributeError, test) - - @patch.object(support, 'SomeClass', spec_set=True) - def test(MockClass): - MockClass.z = 'foo' - - self.assertRaises(AttributeError, test) - - - def test_spec_set_inherit(self): - @patch('%s.SomeClass' % __name__, spec_set=True) - def test(MockClass): - instance = MockClass() - instance.z = 'foo' - - self.assertRaises(AttributeError, test) - - - def test_patch_start_stop(self): - original = something - patcher = patch('%s.something' % __name__) - self.assertIs(something, original) - mock = patcher.start() - try: - self.assertIsNot(mock, original) - self.assertIs(something, mock) - finally: - patcher.stop() - self.assertIs(something, original) - - - def test_stop_without_start(self): - # bpo-36366: calling stop without start will return None. - patcher = patch(foo_name, 'bar', 3) - self.assertIsNone(patcher.stop()) - - - def test_stop_idempotent(self): - # bpo-36366: calling stop on an already stopped patch will return None. - patcher = patch(foo_name, 'bar', 3) - - patcher.start() - patcher.stop() - self.assertIsNone(patcher.stop()) - - - def test_patchobject_start_stop(self): - original = something - patcher = patch.object(PTModule, 'something', 'foo') - self.assertIs(something, original) - replaced = patcher.start() - try: - self.assertEqual(replaced, 'foo') - self.assertIs(something, replaced) - finally: - patcher.stop() - self.assertIs(something, original) - - - def test_patch_dict_start_stop(self): - d = {'foo': 'bar'} - original = d.copy() - patcher = patch.dict(d, [('spam', 'eggs')], clear=True) - self.assertEqual(d, original) - - patcher.start() - try: - self.assertEqual(d, {'spam': 'eggs'}) - finally: - patcher.stop() - self.assertEqual(d, original) - - - def test_patch_dict_stop_without_start(self): - d = {'foo': 'bar'} - original = d.copy() - patcher = patch.dict(d, [('spam', 'eggs')], clear=True) - self.assertFalse(patcher.stop()) - self.assertEqual(d, original) - - - def test_patch_dict_class_decorator(self): - this = self - d = {'spam': 'eggs'} - original = d.copy() - - class Test(object): - def test_first(self): - this.assertEqual(d, {'foo': 'bar'}) - def test_second(self): - this.assertEqual(d, {'foo': 'bar'}) - - Test = patch.dict(d, {'foo': 'bar'}, clear=True)(Test) - self.assertEqual(d, original) - - test = Test() - - test.test_first() - self.assertEqual(d, original) - - test.test_second() - self.assertEqual(d, original) - - test = Test() - - test.test_first() - self.assertEqual(d, original) - - test.test_second() - self.assertEqual(d, original) - - - def test_get_only_proxy(self): - class Something(object): - foo = 'foo' - class SomethingElse: - foo = 'foo' - - for thing in Something, SomethingElse, Something(), SomethingElse: - proxy = _get_proxy(thing) - - @patch.object(proxy, 'foo', 'bar') - def test(): - self.assertEqual(proxy.foo, 'bar') - test() - self.assertEqual(proxy.foo, 'foo') - self.assertEqual(thing.foo, 'foo') - self.assertNotIn('foo', proxy.__dict__) - - - def test_get_set_delete_proxy(self): - class Something(object): - foo = 'foo' - class SomethingElse: - foo = 'foo' - - for thing in Something, SomethingElse, Something(), SomethingElse: - proxy = _get_proxy(Something, get_only=False) - - @patch.object(proxy, 'foo', 'bar') - def test(): - self.assertEqual(proxy.foo, 'bar') - test() - self.assertEqual(proxy.foo, 'foo') - self.assertEqual(thing.foo, 'foo') - self.assertNotIn('foo', proxy.__dict__) - - - def test_patch_keyword_args(self): - kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, - 'foo': MagicMock()} - - patcher = patch(foo_name, **kwargs) - mock = patcher.start() - patcher.stop() - - self.assertRaises(KeyError, mock) - self.assertEqual(mock.foo.bar(), 33) - self.assertIsInstance(mock.foo, MagicMock) - - - def test_patch_object_keyword_args(self): - kwargs = {'side_effect': KeyError, 'foo.bar.return_value': 33, - 'foo': MagicMock()} - - patcher = patch.object(Foo, 'f', **kwargs) - mock = patcher.start() - patcher.stop() - - self.assertRaises(KeyError, mock) - self.assertEqual(mock.foo.bar(), 33) - self.assertIsInstance(mock.foo, MagicMock) - - - def test_patch_dict_keyword_args(self): - original = {'foo': 'bar'} - copy = original.copy() - - patcher = patch.dict(original, foo=3, bar=4, baz=5) - patcher.start() - - try: - self.assertEqual(original, dict(foo=3, bar=4, baz=5)) - finally: - patcher.stop() - - self.assertEqual(original, copy) - - - def test_autospec(self): - class Boo(object): - def __init__(self, a): pass - def f(self, a): pass - def g(self): pass - foo = 'bar' - - class Bar(object): - def a(self): pass - - def _test(mock): - mock(1) - mock.assert_called_with(1) - self.assertRaises(TypeError, mock) - - def _test2(mock): - mock.f(1) - mock.f.assert_called_with(1) - self.assertRaises(TypeError, mock.f) - - mock.g() - mock.g.assert_called_with() - self.assertRaises(TypeError, mock.g, 1) - - self.assertRaises(AttributeError, getattr, mock, 'h') - - mock.foo.lower() - mock.foo.lower.assert_called_with() - self.assertRaises(AttributeError, getattr, mock.foo, 'bar') - - mock.Bar() - mock.Bar.assert_called_with() - - mock.Bar.a() - mock.Bar.a.assert_called_with() - self.assertRaises(TypeError, mock.Bar.a, 1) - - mock.Bar().a() - mock.Bar().a.assert_called_with() - self.assertRaises(TypeError, mock.Bar().a, 1) - - self.assertRaises(AttributeError, getattr, mock.Bar, 'b') - self.assertRaises(AttributeError, getattr, mock.Bar(), 'b') - - def function(mock): - _test(mock) - _test2(mock) - _test2(mock(1)) - self.assertIs(mock, Foo) - return mock - - test = patch(foo_name, autospec=True)(function) - - mock = test() - self.assertIsNot(Foo, mock) - # test patching a second time works - test() - - module = sys.modules[__name__] - test = patch.object(module, 'Foo', autospec=True)(function) - - mock = test() - self.assertIsNot(Foo, mock) - # test patching a second time works - test() - - - def test_autospec_function(self): - @patch('%s.function' % __name__, autospec=True) - def test(mock): - function.assert_not_called() - self.assertRaises(AssertionError, function.assert_called) - self.assertRaises(AssertionError, function.assert_called_once) - function(1) - self.assertRaises(AssertionError, function.assert_not_called) - function.assert_called_with(1) - function.assert_called() - function.assert_called_once() - function(2, 3) - function.assert_called_with(2, 3) - - self.assertRaises(TypeError, function) - self.assertRaises(AttributeError, getattr, function, 'foo') - - test() - - - def test_autospec_keywords(self): - @patch('%s.function' % __name__, autospec=True, - return_value=3) - def test(mock_function): - #self.assertEqual(function.abc, 'foo') - return function(1, 2) - - result = test() - self.assertEqual(result, 3) - - - def test_autospec_staticmethod(self): - with patch('%s.Foo.static_method' % __name__, autospec=True) as method: - Foo.static_method() - method.assert_called_once_with() - - - def test_autospec_classmethod(self): - with patch('%s.Foo.class_method' % __name__, autospec=True) as method: - Foo.class_method() - method.assert_called_once_with() - - - def test_autospec_with_new(self): - patcher = patch('%s.function' % __name__, new=3, autospec=True) - self.assertRaises(TypeError, patcher.start) - - module = sys.modules[__name__] - patcher = patch.object(module, 'function', new=3, autospec=True) - self.assertRaises(TypeError, patcher.start) - - - def test_autospec_with_object(self): - class Bar(Foo): - extra = [] - - patcher = patch(foo_name, autospec=Bar) - mock = patcher.start() - try: - self.assertIsInstance(mock, Bar) - self.assertIsInstance(mock.extra, list) - finally: - patcher.stop() - - - def test_autospec_inherits(self): - FooClass = Foo - patcher = patch(foo_name, autospec=True) - mock = patcher.start() - try: - self.assertIsInstance(mock, FooClass) - self.assertIsInstance(mock(3), FooClass) - finally: - patcher.stop() - - - def test_autospec_name(self): - patcher = patch(foo_name, autospec=True) - mock = patcher.start() - - try: - self.assertIn(" name='Foo'", repr(mock)) - self.assertIn(" name='Foo.f'", repr(mock.f)) - self.assertIn(" name='Foo()'", repr(mock(None))) - self.assertIn(" name='Foo().f'", repr(mock(None).f)) - finally: - patcher.stop() - - - def test_tracebacks(self): - @patch.object(Foo, 'f', object()) - def test(): - raise AssertionError - try: - test() - except: - err = sys.exc_info() - - result = unittest.TextTestResult(None, None, 0) - traceback = result._exc_info_to_string(err, self) - self.assertIn('raise AssertionError', traceback) - - - def test_new_callable_patch(self): - patcher = patch(foo_name, new_callable=NonCallableMagicMock) - - m1 = patcher.start() - patcher.stop() - m2 = patcher.start() - patcher.stop() - - self.assertIsNot(m1, m2) - for mock in m1, m2: - self.assertNotCallable(m1) - - - def test_new_callable_patch_object(self): - patcher = patch.object(Foo, 'f', new_callable=NonCallableMagicMock) - - m1 = patcher.start() - patcher.stop() - m2 = patcher.start() - patcher.stop() - - self.assertIsNot(m1, m2) - for mock in m1, m2: - self.assertNotCallable(m1) - - - def test_new_callable_keyword_arguments(self): - class Bar(object): - kwargs = None - def __init__(self, **kwargs): - Bar.kwargs = kwargs - - patcher = patch(foo_name, new_callable=Bar, arg1=1, arg2=2) - m = patcher.start() - try: - self.assertIs(type(m), Bar) - self.assertEqual(Bar.kwargs, dict(arg1=1, arg2=2)) - finally: - patcher.stop() - - - def test_new_callable_spec(self): - class Bar(object): - kwargs = None - def __init__(self, **kwargs): - Bar.kwargs = kwargs - - patcher = patch(foo_name, new_callable=Bar, spec=Bar) - patcher.start() - try: - self.assertEqual(Bar.kwargs, dict(spec=Bar)) - finally: - patcher.stop() - - patcher = patch(foo_name, new_callable=Bar, spec_set=Bar) - patcher.start() - try: - self.assertEqual(Bar.kwargs, dict(spec_set=Bar)) - finally: - patcher.stop() - - - def test_new_callable_create(self): - non_existent_attr = '%s.weeeee' % foo_name - p = patch(non_existent_attr, new_callable=NonCallableMock) - self.assertRaises(AttributeError, p.start) - - p = patch(non_existent_attr, new_callable=NonCallableMock, - create=True) - m = p.start() - try: - self.assertNotCallable(m, magic=False) - finally: - p.stop() - - - def test_new_callable_incompatible_with_new(self): - self.assertRaises( - ValueError, patch, foo_name, new=object(), new_callable=MagicMock - ) - self.assertRaises( - ValueError, patch.object, Foo, 'f', new=object(), - new_callable=MagicMock - ) - - - def test_new_callable_incompatible_with_autospec(self): - self.assertRaises( - ValueError, patch, foo_name, new_callable=MagicMock, - autospec=True - ) - self.assertRaises( - ValueError, patch.object, Foo, 'f', new_callable=MagicMock, - autospec=True - ) - - - def test_new_callable_inherit_for_mocks(self): - class MockSub(Mock): - pass - - MockClasses = ( - NonCallableMock, NonCallableMagicMock, MagicMock, Mock, MockSub - ) - for Klass in MockClasses: - for arg in 'spec', 'spec_set': - kwargs = {arg: True} - p = patch(foo_name, new_callable=Klass, **kwargs) - m = p.start() - try: - instance = m.return_value - self.assertRaises(AttributeError, getattr, instance, 'x') - finally: - p.stop() - - - def test_new_callable_inherit_non_mock(self): - class NotAMock(object): - def __init__(self, spec): - self.spec = spec - - p = patch(foo_name, new_callable=NotAMock, spec=True) - m = p.start() - try: - self.assertTrue(is_instance(m, NotAMock)) - self.assertRaises(AttributeError, getattr, m, 'return_value') - finally: - p.stop() - - self.assertEqual(m.spec, Foo) - - - def test_new_callable_class_decorating(self): - test = self - original = Foo - class SomeTest(object): - - def _test(self, mock_foo): - test.assertIsNot(Foo, original) - test.assertIs(Foo, mock_foo) - test.assertIsInstance(Foo, SomeClass) - - def test_two(self, mock_foo): - self._test(mock_foo) - def test_one(self, mock_foo): - self._test(mock_foo) - - SomeTest = patch(foo_name, new_callable=SomeClass)(SomeTest) - SomeTest().test_one() - SomeTest().test_two() - self.assertIs(Foo, original) - - - def test_patch_multiple(self): - original_foo = Foo - original_f = Foo.f - original_g = Foo.g - - patcher1 = patch.multiple(foo_name, f=1, g=2) - patcher2 = patch.multiple(Foo, f=1, g=2) - - for patcher in patcher1, patcher2: - patcher.start() - try: - self.assertIs(Foo, original_foo) - self.assertEqual(Foo.f, 1) - self.assertEqual(Foo.g, 2) - finally: - patcher.stop() - - self.assertIs(Foo, original_foo) - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - @patch.multiple(foo_name, f=3, g=4) - def test(): - self.assertIs(Foo, original_foo) - self.assertEqual(Foo.f, 3) - self.assertEqual(Foo.g, 4) - - test() - - - def test_patch_multiple_no_kwargs(self): - self.assertRaises(ValueError, patch.multiple, foo_name) - self.assertRaises(ValueError, patch.multiple, Foo) - - - def test_patch_multiple_create_mocks(self): - original_foo = Foo - original_f = Foo.f - original_g = Foo.g - - @patch.multiple(foo_name, f=DEFAULT, g=3, foo=DEFAULT) - def test(f, foo): - self.assertIs(Foo, original_foo) - self.assertIs(Foo.f, f) - self.assertEqual(Foo.g, 3) - self.assertIs(Foo.foo, foo) - self.assertTrue(is_instance(f, MagicMock)) - self.assertTrue(is_instance(foo, MagicMock)) - - test() - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_create_mocks_different_order(self): - original_f = Foo.f - original_g = Foo.g - - patcher = patch.object(Foo, 'f', 3) - patcher.attribute_name = 'f' - - other = patch.object(Foo, 'g', DEFAULT) - other.attribute_name = 'g' - patcher.additional_patchers = [other] - - @patcher - def test(g): - self.assertIs(Foo.g, g) - self.assertEqual(Foo.f, 3) - - test() - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_stacked_decorators(self): - original_foo = Foo - original_f = Foo.f - original_g = Foo.g - - @patch.multiple(foo_name, f=DEFAULT) - @patch.multiple(foo_name, foo=DEFAULT) - @patch(foo_name + '.g') - def test1(g, **kwargs): - _test(g, **kwargs) - - @patch.multiple(foo_name, f=DEFAULT) - @patch(foo_name + '.g') - @patch.multiple(foo_name, foo=DEFAULT) - def test2(g, **kwargs): - _test(g, **kwargs) - - @patch(foo_name + '.g') - @patch.multiple(foo_name, f=DEFAULT) - @patch.multiple(foo_name, foo=DEFAULT) - def test3(g, **kwargs): - _test(g, **kwargs) - - def _test(g, **kwargs): - f = kwargs.pop('f') - foo = kwargs.pop('foo') - self.assertFalse(kwargs) - - self.assertIs(Foo, original_foo) - self.assertIs(Foo.f, f) - self.assertIs(Foo.g, g) - self.assertIs(Foo.foo, foo) - self.assertTrue(is_instance(f, MagicMock)) - self.assertTrue(is_instance(g, MagicMock)) - self.assertTrue(is_instance(foo, MagicMock)) - - test1() - test2() - test3() - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_create_mocks_patcher(self): - original_foo = Foo - original_f = Foo.f - original_g = Foo.g - - patcher = patch.multiple(foo_name, f=DEFAULT, g=3, foo=DEFAULT) - - result = patcher.start() - try: - f = result['f'] - foo = result['foo'] - self.assertEqual(set(result), set(['f', 'foo'])) - - self.assertIs(Foo, original_foo) - self.assertIs(Foo.f, f) - self.assertIs(Foo.foo, foo) - self.assertTrue(is_instance(f, MagicMock)) - self.assertTrue(is_instance(foo, MagicMock)) - finally: - patcher.stop() - - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_decorating_class(self): - test = self - original_foo = Foo - original_f = Foo.f - original_g = Foo.g - - class SomeTest(object): - - def _test(self, f, foo): - test.assertIs(Foo, original_foo) - test.assertIs(Foo.f, f) - test.assertEqual(Foo.g, 3) - test.assertIs(Foo.foo, foo) - test.assertTrue(is_instance(f, MagicMock)) - test.assertTrue(is_instance(foo, MagicMock)) - - def test_two(self, f, foo): - self._test(f, foo) - def test_one(self, f, foo): - self._test(f, foo) - - SomeTest = patch.multiple( - foo_name, f=DEFAULT, g=3, foo=DEFAULT - )(SomeTest) - - thing = SomeTest() - thing.test_one() - thing.test_two() - - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_create(self): - patcher = patch.multiple(Foo, blam='blam') - self.assertRaises(AttributeError, patcher.start) - - patcher = patch.multiple(Foo, blam='blam', create=True) - patcher.start() - try: - self.assertEqual(Foo.blam, 'blam') - finally: - patcher.stop() - - self.assertFalse(hasattr(Foo, 'blam')) - - - def test_patch_multiple_spec_set(self): - # if spec_set works then we can assume that spec and autospec also - # work as the underlying machinery is the same - patcher = patch.multiple(Foo, foo=DEFAULT, spec_set=['a', 'b']) - result = patcher.start() - try: - self.assertEqual(Foo.foo, result['foo']) - Foo.foo.a(1) - Foo.foo.b(2) - Foo.foo.a.assert_called_with(1) - Foo.foo.b.assert_called_with(2) - self.assertRaises(AttributeError, setattr, Foo.foo, 'c', None) - finally: - patcher.stop() - - - def test_patch_multiple_new_callable(self): - class Thing(object): - pass - - patcher = patch.multiple( - Foo, f=DEFAULT, g=DEFAULT, new_callable=Thing - ) - result = patcher.start() - try: - self.assertIs(Foo.f, result['f']) - self.assertIs(Foo.g, result['g']) - self.assertIsInstance(Foo.f, Thing) - self.assertIsInstance(Foo.g, Thing) - self.assertIsNot(Foo.f, Foo.g) - finally: - patcher.stop() - - - def test_nested_patch_failure(self): - original_f = Foo.f - original_g = Foo.g - - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'missing', 1) - @patch.object(Foo, 'f', 1) - def thing1(): pass - - @patch.object(Foo, 'missing', 1) - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'f', 1) - def thing2(): pass - - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'f', 1) - @patch.object(Foo, 'missing', 1) - def thing3(): pass - - for func in thing1, thing2, thing3: - self.assertRaises(AttributeError, func) - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_new_callable_failure(self): - original_f = Foo.f - original_g = Foo.g - original_foo = Foo.foo - - def crasher(): - raise NameError('crasher') - - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'foo', new_callable=crasher) - @patch.object(Foo, 'f', 1) - def thing1(): pass - - @patch.object(Foo, 'foo', new_callable=crasher) - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'f', 1) - def thing2(): pass - - @patch.object(Foo, 'g', 1) - @patch.object(Foo, 'f', 1) - @patch.object(Foo, 'foo', new_callable=crasher) - def thing3(): pass - - for func in thing1, thing2, thing3: - self.assertRaises(NameError, func) - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - self.assertEqual(Foo.foo, original_foo) - - - def test_patch_multiple_failure(self): - original_f = Foo.f - original_g = Foo.g - - patcher = patch.object(Foo, 'f', 1) - patcher.attribute_name = 'f' - - good = patch.object(Foo, 'g', 1) - good.attribute_name = 'g' - - bad = patch.object(Foo, 'missing', 1) - bad.attribute_name = 'missing' - - for additionals in [good, bad], [bad, good]: - patcher.additional_patchers = additionals - - @patcher - def func(): pass - - self.assertRaises(AttributeError, func) - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - - - def test_patch_multiple_new_callable_failure(self): - original_f = Foo.f - original_g = Foo.g - original_foo = Foo.foo - - def crasher(): - raise NameError('crasher') - - patcher = patch.object(Foo, 'f', 1) - patcher.attribute_name = 'f' - - good = patch.object(Foo, 'g', 1) - good.attribute_name = 'g' - - bad = patch.object(Foo, 'foo', new_callable=crasher) - bad.attribute_name = 'foo' - - for additionals in [good, bad], [bad, good]: - patcher.additional_patchers = additionals - - @patcher - def func(): pass - - self.assertRaises(NameError, func) - self.assertEqual(Foo.f, original_f) - self.assertEqual(Foo.g, original_g) - self.assertEqual(Foo.foo, original_foo) - - - def test_patch_multiple_string_subclasses(self): - Foo = type('Foo', (str,), {'fish': 'tasty'}) - foo = Foo() - @patch.multiple(foo, fish='nearly gone') - def test(): - self.assertEqual(foo.fish, 'nearly gone') - - test() - self.assertEqual(foo.fish, 'tasty') - - - @patch('unittest.mock.patch.TEST_PREFIX', 'foo') - def test_patch_test_prefix(self): - class Foo(object): - thing = 'original' - - def foo_one(self): - return self.thing - def foo_two(self): - return self.thing - def test_one(self): - return self.thing - def test_two(self): - return self.thing - - Foo = patch.object(Foo, 'thing', 'changed')(Foo) - - foo = Foo() - self.assertEqual(foo.foo_one(), 'changed') - self.assertEqual(foo.foo_two(), 'changed') - self.assertEqual(foo.test_one(), 'original') - self.assertEqual(foo.test_two(), 'original') - - - @patch('unittest.mock.patch.TEST_PREFIX', 'bar') - def test_patch_dict_test_prefix(self): - class Foo(object): - def bar_one(self): - return dict(the_dict) - def bar_two(self): - return dict(the_dict) - def test_one(self): - return dict(the_dict) - def test_two(self): - return dict(the_dict) - - the_dict = {'key': 'original'} - Foo = patch.dict(the_dict, key='changed')(Foo) - - foo =Foo() - self.assertEqual(foo.bar_one(), {'key': 'changed'}) - self.assertEqual(foo.bar_two(), {'key': 'changed'}) - self.assertEqual(foo.test_one(), {'key': 'original'}) - self.assertEqual(foo.test_two(), {'key': 'original'}) - - - def test_patch_with_spec_mock_repr(self): - for arg in ('spec', 'autospec', 'spec_set'): - p = patch('%s.SomeClass' % __name__, **{arg: True}) - m = p.start() - try: - self.assertIn(" name='SomeClass'", repr(m)) - self.assertIn(" name='SomeClass.class_attribute'", - repr(m.class_attribute)) - self.assertIn(" name='SomeClass()'", repr(m())) - self.assertIn(" name='SomeClass().class_attribute'", - repr(m().class_attribute)) - finally: - p.stop() - - - def test_patch_nested_autospec_repr(self): - with patch('unittest.test.testmock.support', autospec=True) as m: - self.assertIn(" name='support.SomeClass.wibble()'", - repr(m.SomeClass.wibble())) - self.assertIn(" name='support.SomeClass().wibble()'", - repr(m.SomeClass().wibble())) - - - - def test_mock_calls_with_patch(self): - for arg in ('spec', 'autospec', 'spec_set'): - p = patch('%s.SomeClass' % __name__, **{arg: True}) - m = p.start() - try: - m.wibble() - - kalls = [call.wibble()] - self.assertEqual(m.mock_calls, kalls) - self.assertEqual(m.method_calls, kalls) - self.assertEqual(m.wibble.mock_calls, [call()]) - - result = m() - kalls.append(call()) - self.assertEqual(m.mock_calls, kalls) - - result.wibble() - kalls.append(call().wibble()) - self.assertEqual(m.mock_calls, kalls) - - self.assertEqual(result.mock_calls, [call.wibble()]) - self.assertEqual(result.wibble.mock_calls, [call()]) - self.assertEqual(result.method_calls, [call.wibble()]) - finally: - p.stop() - - - def test_patch_imports_lazily(self): - p1 = patch('squizz.squozz') - self.assertRaises(ImportError, p1.start) - - with uncache('squizz'): - squizz = Mock() - sys.modules['squizz'] = squizz - - squizz.squozz = 6 - p1 = patch('squizz.squozz') - squizz.squozz = 3 - p1.start() - p1.stop() - self.assertEqual(squizz.squozz, 3) - - def test_patch_propagates_exc_on_exit(self): - class holder: - exc_info = None, None, None - - class custom_patch(_patch): - def __exit__(self, etype=None, val=None, tb=None): - _patch.__exit__(self, etype, val, tb) - holder.exc_info = etype, val, tb - stop = __exit__ - - def with_custom_patch(target): - getter, attribute = _get_target(target) - return custom_patch( - getter, attribute, DEFAULT, None, False, None, - None, None, {} - ) - - @with_custom_patch('squizz.squozz') - def test(mock): - raise RuntimeError - - with uncache('squizz'): - squizz = Mock() - sys.modules['squizz'] = squizz - - self.assertRaises(RuntimeError, test) - - self.assertIs(holder.exc_info[0], RuntimeError) - self.assertIsNotNone(holder.exc_info[1], - 'exception value not propagated') - self.assertIsNotNone(holder.exc_info[2], - 'exception traceback not propagated') - - - def test_create_and_specs(self): - for kwarg in ('spec', 'spec_set', 'autospec'): - p = patch('%s.doesnotexist' % __name__, create=True, - **{kwarg: True}) - self.assertRaises(TypeError, p.start) - self.assertRaises(NameError, lambda: doesnotexist) - - # check that spec with create is innocuous if the original exists - p = patch(MODNAME, create=True, **{kwarg: True}) - p.start() - p.stop() - - - def test_multiple_specs(self): - original = PTModule - for kwarg in ('spec', 'spec_set'): - p = patch(MODNAME, autospec=0, **{kwarg: 0}) - self.assertRaises(TypeError, p.start) - self.assertIs(PTModule, original) - - for kwarg in ('spec', 'autospec'): - p = patch(MODNAME, spec_set=0, **{kwarg: 0}) - self.assertRaises(TypeError, p.start) - self.assertIs(PTModule, original) - - for kwarg in ('spec_set', 'autospec'): - p = patch(MODNAME, spec=0, **{kwarg: 0}) - self.assertRaises(TypeError, p.start) - self.assertIs(PTModule, original) - - - def test_specs_false_instead_of_none(self): - p = patch(MODNAME, spec=False, spec_set=False, autospec=False) - mock = p.start() - try: - # no spec should have been set, so attribute access should not fail - mock.does_not_exist - mock.does_not_exist = 3 - finally: - p.stop() - - - def test_falsey_spec(self): - for kwarg in ('spec', 'autospec', 'spec_set'): - p = patch(MODNAME, **{kwarg: 0}) - m = p.start() - try: - self.assertRaises(AttributeError, getattr, m, 'doesnotexit') - finally: - p.stop() - - - def test_spec_set_true(self): - for kwarg in ('spec', 'autospec'): - p = patch(MODNAME, spec_set=True, **{kwarg: True}) - m = p.start() - try: - self.assertRaises(AttributeError, setattr, m, - 'doesnotexist', 'something') - self.assertRaises(AttributeError, getattr, m, 'doesnotexist') - finally: - p.stop() - - - def test_callable_spec_as_list(self): - spec = ('__call__',) - p = patch(MODNAME, spec=spec) - m = p.start() - try: - self.assertTrue(callable(m)) - finally: - p.stop() - - - def test_not_callable_spec_as_list(self): - spec = ('foo', 'bar') - p = patch(MODNAME, spec=spec) - m = p.start() - try: - self.assertFalse(callable(m)) - finally: - p.stop() - - - def test_patch_stopall(self): - unlink = os.unlink - chdir = os.chdir - path = os.path - patch('os.unlink', something).start() - patch('os.chdir', something_else).start() - - @patch('os.path') - def patched(mock_path): - patch.stopall() - self.assertIs(os.path, mock_path) - self.assertIs(os.unlink, unlink) - self.assertIs(os.chdir, chdir) - - patched() - self.assertIs(os.path, path) - - def test_stopall_lifo(self): - stopped = [] - class thing(object): - one = two = three = None - - def get_patch(attribute): - class mypatch(_patch): - def stop(self): - stopped.append(attribute) - return super(mypatch, self).stop() - return mypatch(lambda: thing, attribute, None, None, - False, None, None, None, {}) - [get_patch(val).start() for val in ("one", "two", "three")] - patch.stopall() - - self.assertEqual(stopped, ["three", "two", "one"]) - - def test_patch_dict_stopall(self): - dic1 = {} - dic2 = {1: 'a'} - dic3 = {1: 'A', 2: 'B'} - origdic1 = dic1.copy() - origdic2 = dic2.copy() - origdic3 = dic3.copy() - patch.dict(dic1, {1: 'I', 2: 'II'}).start() - patch.dict(dic2, {2: 'b'}).start() - - @patch.dict(dic3) - def patched(): - del dic3[1] - - patched() - self.assertNotEqual(dic1, origdic1) - self.assertNotEqual(dic2, origdic2) - self.assertEqual(dic3, origdic3) - - patch.stopall() - - self.assertEqual(dic1, origdic1) - self.assertEqual(dic2, origdic2) - self.assertEqual(dic3, origdic3) - - - def test_patch_and_patch_dict_stopall(self): - original_unlink = os.unlink - original_chdir = os.chdir - dic1 = {} - dic2 = {1: 'A', 2: 'B'} - origdic1 = dic1.copy() - origdic2 = dic2.copy() - - patch('os.unlink', something).start() - patch('os.chdir', something_else).start() - patch.dict(dic1, {1: 'I', 2: 'II'}).start() - patch.dict(dic2).start() - del dic2[1] - - self.assertIsNot(os.unlink, original_unlink) - self.assertIsNot(os.chdir, original_chdir) - self.assertNotEqual(dic1, origdic1) - self.assertNotEqual(dic2, origdic2) - patch.stopall() - self.assertIs(os.unlink, original_unlink) - self.assertIs(os.chdir, original_chdir) - self.assertEqual(dic1, origdic1) - self.assertEqual(dic2, origdic2) - - - def test_special_attrs(self): - def foo(x=0): - """TEST""" - return x - with patch.object(foo, '__defaults__', (1, )): - self.assertEqual(foo(), 1) - self.assertEqual(foo(), 0) - - with patch.object(foo, '__doc__', "FUN"): - self.assertEqual(foo.__doc__, "FUN") - self.assertEqual(foo.__doc__, "TEST") - - with patch.object(foo, '__module__', "testpatch2"): - self.assertEqual(foo.__module__, "testpatch2") - self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch') - - with patch.object(foo, '__annotations__', dict([('s', 1, )])): - self.assertEqual(foo.__annotations__, dict([('s', 1, )])) - self.assertEqual(foo.__annotations__, dict()) - - def foo(*a, x=0): - return x - with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): - self.assertEqual(foo(), 1) - self.assertEqual(foo(), 0) - - def test_patch_orderdict(self): - foo = OrderedDict() - foo['a'] = object() - foo['b'] = 'python' - - original = foo.copy() - update_values = list(zip('cdefghijklmnopqrstuvwxyz', range(26))) - patched_values = list(foo.items()) + update_values - - with patch.dict(foo, OrderedDict(update_values)): - self.assertEqual(list(foo.items()), patched_values) - - self.assertEqual(foo, original) - - with patch.dict(foo, update_values): - self.assertEqual(list(foo.items()), patched_values) - - self.assertEqual(foo, original) - - def test_dotted_but_module_not_loaded(self): - # This exercises the AttributeError branch of _dot_lookup. - - # make sure it's there - import unittest.test.testmock.support - # now make sure it's not: - with patch.dict('sys.modules'): - del sys.modules['unittest.test.testmock.support'] - del sys.modules['unittest.test.testmock'] - del sys.modules['unittest.test'] - del sys.modules['unittest'] - - # now make sure we can patch based on a dotted path: - @patch('unittest.test.testmock.support.X') - def test(mock): - pass - test() - - - def test_invalid_target(self): - with self.assertRaises(TypeError): - patch('') - - - def test_cant_set_kwargs_when_passing_a_mock(self): - @patch('unittest.test.testmock.support.X', new=object(), x=1) - def test(): pass - with self.assertRaises(TypeError): - test() - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py b/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py deleted file mode 100644 index 59f52338d4..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testsealable.py +++ /dev/null @@ -1,176 +0,0 @@ -import unittest -from unittest import mock - - -class SampleObject: - - def method_sample1(self): pass - - def method_sample2(self): pass - - -class TestSealable(unittest.TestCase): - - def test_attributes_return_more_mocks_by_default(self): - m = mock.Mock() - - self.assertIsInstance(m.test, mock.Mock) - self.assertIsInstance(m.test(), mock.Mock) - self.assertIsInstance(m.test().test2(), mock.Mock) - - def test_new_attributes_cannot_be_accessed_on_seal(self): - m = mock.Mock() - - mock.seal(m) - with self.assertRaises(AttributeError): - m.test - with self.assertRaises(AttributeError): - m() - - def test_new_attributes_cannot_be_set_on_seal(self): - m = mock.Mock() - - mock.seal(m) - with self.assertRaises(AttributeError): - m.test = 1 - - def test_existing_attributes_can_be_set_on_seal(self): - m = mock.Mock() - m.test.test2 = 1 - - mock.seal(m) - m.test.test2 = 2 - self.assertEqual(m.test.test2, 2) - - def test_new_attributes_cannot_be_set_on_child_of_seal(self): - m = mock.Mock() - m.test.test2 = 1 - - mock.seal(m) - with self.assertRaises(AttributeError): - m.test.test3 = 1 - - def test_existing_attributes_allowed_after_seal(self): - m = mock.Mock() - - m.test.return_value = 3 - - mock.seal(m) - self.assertEqual(m.test(), 3) - - def test_initialized_attributes_allowed_after_seal(self): - m = mock.Mock(test_value=1) - - mock.seal(m) - self.assertEqual(m.test_value, 1) - - def test_call_on_sealed_mock_fails(self): - m = mock.Mock() - - mock.seal(m) - with self.assertRaises(AttributeError): - m() - - def test_call_on_defined_sealed_mock_succeeds(self): - m = mock.Mock(return_value=5) - - mock.seal(m) - self.assertEqual(m(), 5) - - def test_seals_recurse_on_added_attributes(self): - m = mock.Mock() - - m.test1.test2().test3 = 4 - - mock.seal(m) - self.assertEqual(m.test1.test2().test3, 4) - with self.assertRaises(AttributeError): - m.test1.test2().test4 - with self.assertRaises(AttributeError): - m.test1.test3 - - def test_seals_recurse_on_magic_methods(self): - m = mock.MagicMock() - - m.test1.test2["a"].test3 = 4 - m.test1.test3[2:5].test3 = 4 - - mock.seal(m) - self.assertEqual(m.test1.test2["a"].test3, 4) - self.assertEqual(m.test1.test2[2:5].test3, 4) - with self.assertRaises(AttributeError): - m.test1.test2["a"].test4 - with self.assertRaises(AttributeError): - m.test1.test3[2:5].test4 - - def test_seals_dont_recurse_on_manual_attributes(self): - m = mock.Mock(name="root_mock") - - m.test1.test2 = mock.Mock(name="not_sealed") - m.test1.test2.test3 = 4 - - mock.seal(m) - self.assertEqual(m.test1.test2.test3, 4) - m.test1.test2.test4 # Does not raise - m.test1.test2.test4 = 1 # Does not raise - - def test_integration_with_spec_att_definition(self): - """You are not restricted when using mock with spec""" - m = mock.Mock(SampleObject) - - m.attr_sample1 = 1 - m.attr_sample3 = 3 - - mock.seal(m) - self.assertEqual(m.attr_sample1, 1) - self.assertEqual(m.attr_sample3, 3) - with self.assertRaises(AttributeError): - m.attr_sample2 - - def test_integration_with_spec_method_definition(self): - """You need to defin the methods, even if they are in the spec""" - m = mock.Mock(SampleObject) - - m.method_sample1.return_value = 1 - - mock.seal(m) - self.assertEqual(m.method_sample1(), 1) - with self.assertRaises(AttributeError): - m.method_sample2() - - def test_integration_with_spec_method_definition_respects_spec(self): - """You cannot define methods out of the spec""" - m = mock.Mock(SampleObject) - - with self.assertRaises(AttributeError): - m.method_sample3.return_value = 3 - - def test_sealed_exception_has_attribute_name(self): - m = mock.Mock() - - mock.seal(m) - with self.assertRaises(AttributeError) as cm: - m.SECRETE_name - self.assertIn("SECRETE_name", str(cm.exception)) - - def test_attribute_chain_is_maintained(self): - m = mock.Mock(name="mock_name") - m.test1.test2.test3.test4 - - mock.seal(m) - with self.assertRaises(AttributeError) as cm: - m.test1.test2.test3.test4.boom - self.assertIn("mock_name.test1.test2.test3.test4.boom", str(cm.exception)) - - def test_call_chain_is_maintained(self): - m = mock.Mock() - m.test1().test2.test3().test4 - - mock.seal(m) - with self.assertRaises(AttributeError) as cm: - m.test1().test2.test3().test4() - self.assertIn("mock.test1().test2.test3().test4", str(cm.exception)) - - -if __name__ == "__main__": - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py b/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py deleted file mode 100644 index de53509803..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testsentinel.py +++ /dev/null @@ -1,41 +0,0 @@ -import unittest -import copy -import pickle -from unittest.mock import sentinel, DEFAULT - - -class SentinelTest(unittest.TestCase): - - def testSentinels(self): - self.assertEqual(sentinel.whatever, sentinel.whatever, - 'sentinel not stored') - self.assertNotEqual(sentinel.whatever, sentinel.whateverelse, - 'sentinel should be unique') - - - def testSentinelName(self): - self.assertEqual(str(sentinel.whatever), 'sentinel.whatever', - 'sentinel name incorrect') - - - def testDEFAULT(self): - self.assertIs(DEFAULT, sentinel.DEFAULT) - - def testBases(self): - # If this doesn't raise an AttributeError then help(mock) is broken - self.assertRaises(AttributeError, lambda: sentinel.__bases__) - - def testPickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL+1): - with self.subTest(protocol=proto): - pickled = pickle.dumps(sentinel.whatever, proto) - unpickled = pickle.loads(pickled) - self.assertIs(unpickled, sentinel.whatever) - - def testCopy(self): - self.assertIs(copy.copy(sentinel.whatever), sentinel.whatever) - self.assertIs(copy.deepcopy(sentinel.whatever), sentinel.whatever) - - -if __name__ == '__main__': - unittest.main() diff --git a/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py b/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py deleted file mode 100644 index 42ebf3898c..0000000000 --- a/Monika After Story/game/python-packages/unittest/test/testmock/testwith.py +++ /dev/null @@ -1,347 +0,0 @@ -import unittest -from warnings import catch_warnings - -from unittest.test.testmock.support import is_instance -from unittest.mock import MagicMock, Mock, patch, sentinel, mock_open, call - - - -something = sentinel.Something -something_else = sentinel.SomethingElse - - -class SampleException(Exception): pass - - -class WithTest(unittest.TestCase): - - def test_with_statement(self): - with patch('%s.something' % __name__, sentinel.Something2): - self.assertEqual(something, sentinel.Something2, "unpatched") - self.assertEqual(something, sentinel.Something) - - - def test_with_statement_exception(self): - with self.assertRaises(SampleException): - with patch('%s.something' % __name__, sentinel.Something2): - self.assertEqual(something, sentinel.Something2, "unpatched") - raise SampleException() - self.assertEqual(something, sentinel.Something) - - - def test_with_statement_as(self): - with patch('%s.something' % __name__) as mock_something: - self.assertEqual(something, mock_something, "unpatched") - self.assertTrue(is_instance(mock_something, MagicMock), - "patching wrong type") - self.assertEqual(something, sentinel.Something) - - - def test_patch_object_with_statement(self): - class Foo(object): - something = 'foo' - original = Foo.something - with patch.object(Foo, 'something'): - self.assertNotEqual(Foo.something, original, "unpatched") - self.assertEqual(Foo.something, original) - - - def test_with_statement_nested(self): - with catch_warnings(record=True): - with patch('%s.something' % __name__) as mock_something, patch('%s.something_else' % __name__) as mock_something_else: - self.assertEqual(something, mock_something, "unpatched") - self.assertEqual(something_else, mock_something_else, - "unpatched") - - self.assertEqual(something, sentinel.Something) - self.assertEqual(something_else, sentinel.SomethingElse) - - - def test_with_statement_specified(self): - with patch('%s.something' % __name__, sentinel.Patched) as mock_something: - self.assertEqual(something, mock_something, "unpatched") - self.assertEqual(mock_something, sentinel.Patched, "wrong patch") - self.assertEqual(something, sentinel.Something) - - - def testContextManagerMocking(self): - mock = Mock() - mock.__enter__ = Mock() - mock.__exit__ = Mock() - mock.__exit__.return_value = False - - with mock as m: - self.assertEqual(m, mock.__enter__.return_value) - mock.__enter__.assert_called_with() - mock.__exit__.assert_called_with(None, None, None) - - - def test_context_manager_with_magic_mock(self): - mock = MagicMock() - - with self.assertRaises(TypeError): - with mock: - 'foo' + 3 - mock.__enter__.assert_called_with() - self.assertTrue(mock.__exit__.called) - - - def test_with_statement_same_attribute(self): - with patch('%s.something' % __name__, sentinel.Patched) as mock_something: - self.assertEqual(something, mock_something, "unpatched") - - with patch('%s.something' % __name__) as mock_again: - self.assertEqual(something, mock_again, "unpatched") - - self.assertEqual(something, mock_something, - "restored with wrong instance") - - self.assertEqual(something, sentinel.Something, "not restored") - - - def test_with_statement_imbricated(self): - with patch('%s.something' % __name__) as mock_something: - self.assertEqual(something, mock_something, "unpatched") - - with patch('%s.something_else' % __name__) as mock_something_else: - self.assertEqual(something_else, mock_something_else, - "unpatched") - - self.assertEqual(something, sentinel.Something) - self.assertEqual(something_else, sentinel.SomethingElse) - - - def test_dict_context_manager(self): - foo = {} - with patch.dict(foo, {'a': 'b'}): - self.assertEqual(foo, {'a': 'b'}) - self.assertEqual(foo, {}) - - with self.assertRaises(NameError): - with patch.dict(foo, {'a': 'b'}): - self.assertEqual(foo, {'a': 'b'}) - raise NameError('Konrad') - - self.assertEqual(foo, {}) - - def test_double_patch_instance_method(self): - class C: - def f(self): pass - - c = C() - - with patch.object(c, 'f', autospec=True) as patch1: - with patch.object(c, 'f', autospec=True) as patch2: - c.f() - self.assertEqual(patch2.call_count, 1) - self.assertEqual(patch1.call_count, 0) - c.f() - self.assertEqual(patch1.call_count, 1) - - -class TestMockOpen(unittest.TestCase): - - def test_mock_open(self): - mock = mock_open() - with patch('%s.open' % __name__, mock, create=True) as patched: - self.assertIs(patched, mock) - open('foo') - - mock.assert_called_once_with('foo') - - - def test_mock_open_context_manager(self): - mock = mock_open() - handle = mock.return_value - with patch('%s.open' % __name__, mock, create=True): - with open('foo') as f: - f.read() - - expected_calls = [call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] - self.assertEqual(mock.mock_calls, expected_calls) - self.assertIs(f, handle) - - def test_mock_open_context_manager_multiple_times(self): - mock = mock_open() - with patch('%s.open' % __name__, mock, create=True): - with open('foo') as f: - f.read() - with open('bar') as f: - f.read() - - expected_calls = [ - call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None), - call('bar'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] - self.assertEqual(mock.mock_calls, expected_calls) - - def test_explicit_mock(self): - mock = MagicMock() - mock_open(mock) - - with patch('%s.open' % __name__, mock, create=True) as patched: - self.assertIs(patched, mock) - open('foo') - - mock.assert_called_once_with('foo') - - - def test_read_data(self): - mock = mock_open(read_data='foo') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - result = h.read() - - self.assertEqual(result, 'foo') - - - def test_readline_data(self): - # Check that readline will return all the lines from the fake file - # And that once fully consumed, readline will return an empty string. - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - line1 = h.readline() - line2 = h.readline() - line3 = h.readline() - self.assertEqual(line1, 'foo\n') - self.assertEqual(line2, 'bar\n') - self.assertEqual(line3, 'baz\n') - self.assertEqual(h.readline(), '') - - # Check that we properly emulate a file that doesn't end in a newline - mock = mock_open(read_data='foo') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - result = h.readline() - self.assertEqual(result, 'foo') - self.assertEqual(h.readline(), '') - - - def test_dunder_iter_data(self): - # Check that dunder_iter will return all the lines from the fake file. - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - lines = [l for l in h] - self.assertEqual(lines[0], 'foo\n') - self.assertEqual(lines[1], 'bar\n') - self.assertEqual(lines[2], 'baz\n') - self.assertEqual(h.readline(), '') - with self.assertRaises(StopIteration): - next(h) - - def test_next_data(self): - # Check that next will correctly return the next available - # line and plays well with the dunder_iter part. - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - line1 = next(h) - line2 = next(h) - lines = [l for l in h] - self.assertEqual(line1, 'foo\n') - self.assertEqual(line2, 'bar\n') - self.assertEqual(lines[0], 'baz\n') - self.assertEqual(h.readline(), '') - - def test_readlines_data(self): - # Test that emulating a file that ends in a newline character works - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - result = h.readlines() - self.assertEqual(result, ['foo\n', 'bar\n', 'baz\n']) - - # Test that files without a final newline will also be correctly - # emulated - mock = mock_open(read_data='foo\nbar\nbaz') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - result = h.readlines() - - self.assertEqual(result, ['foo\n', 'bar\n', 'baz']) - - - def test_read_bytes(self): - mock = mock_open(read_data=b'\xc6') - with patch('%s.open' % __name__, mock, create=True): - with open('abc', 'rb') as f: - result = f.read() - self.assertEqual(result, b'\xc6') - - - def test_readline_bytes(self): - m = mock_open(read_data=b'abc\ndef\nghi\n') - with patch('%s.open' % __name__, m, create=True): - with open('abc', 'rb') as f: - line1 = f.readline() - line2 = f.readline() - line3 = f.readline() - self.assertEqual(line1, b'abc\n') - self.assertEqual(line2, b'def\n') - self.assertEqual(line3, b'ghi\n') - - - def test_readlines_bytes(self): - m = mock_open(read_data=b'abc\ndef\nghi\n') - with patch('%s.open' % __name__, m, create=True): - with open('abc', 'rb') as f: - result = f.readlines() - self.assertEqual(result, [b'abc\n', b'def\n', b'ghi\n']) - - - def test_mock_open_read_with_argument(self): - # At one point calling read with an argument was broken - # for mocks returned by mock_open - some_data = 'foo\nbar\nbaz' - mock = mock_open(read_data=some_data) - self.assertEqual(mock().read(10), some_data[:10]) - self.assertEqual(mock().read(10), some_data[:10]) - - f = mock() - self.assertEqual(f.read(10), some_data[:10]) - self.assertEqual(f.read(10), some_data[10:]) - - - def test_interleaved_reads(self): - # Test that calling read, readline, and readlines pulls data - # sequentially from the data we preload with - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - line1 = h.readline() - rest = h.readlines() - self.assertEqual(line1, 'foo\n') - self.assertEqual(rest, ['bar\n', 'baz\n']) - - mock = mock_open(read_data='foo\nbar\nbaz\n') - with patch('%s.open' % __name__, mock, create=True): - h = open('bar') - line1 = h.readline() - rest = h.read() - self.assertEqual(line1, 'foo\n') - self.assertEqual(rest, 'bar\nbaz\n') - - - def test_overriding_return_values(self): - mock = mock_open(read_data='foo') - handle = mock() - - handle.read.return_value = 'bar' - handle.readline.return_value = 'bar' - handle.readlines.return_value = ['bar'] - - self.assertEqual(handle.read(), 'bar') - self.assertEqual(handle.readline(), 'bar') - self.assertEqual(handle.readlines(), ['bar']) - - # call repeatedly to check that a StopIteration is not propagated - self.assertEqual(handle.readline(), 'bar') - self.assertEqual(handle.readline(), 'bar') - - -if __name__ == '__main__': - unittest.main() From 92b803d411435b0475aebd087117a75375ac2dd7 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 18:42:56 +0300 Subject: [PATCH 133/180] update `winnie32api` --- .../python-packages/winnie32api/__init__.py | 2 +- .../python-packages/winnie32api/common.py | 6 +-- .../python-packages/winnie32api/notifs.py | 51 ++++++++++++++++--- .../python-packages/winnie32api/windows.py | 2 +- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/Monika After Story/game/python-packages/winnie32api/__init__.py b/Monika After Story/game/python-packages/winnie32api/__init__.py index 702dbc82d2..effa811858 100644 --- a/Monika After Story/game/python-packages/winnie32api/__init__.py +++ b/Monika After Story/game/python-packages/winnie32api/__init__.py @@ -8,7 +8,7 @@ __title__ = "winnie32api" __author__ = "Booplicate" -__version__ = "0.0.1" +__version__ = "0.0.3" import ctypes diff --git a/Monika After Story/game/python-packages/winnie32api/common.py b/Monika After Story/game/python-packages/winnie32api/common.py index 34d3fd79f9..817699743d 100644 --- a/Monika After Story/game/python-packages/winnie32api/common.py +++ b/Monika After Story/game/python-packages/winnie32api/common.py @@ -48,13 +48,13 @@ class Rect(NamedTuple): bottom_right: Point @classmethod - def from_coords(cls, top: Coord, left: Coord, bottom: Coord, right: Coord) -> Rect: + def from_coords(cls, left: Coord, top: Coord, right: Coord, bottom: Coord) -> Rect: """ Constructs a rect from 4 coordinates """ return cls( - Point(top, left), - Point(bottom, right) + Point(left, top), + Point(right, bottom) ) diff --git a/Monika After Story/game/python-packages/winnie32api/notifs.py b/Monika After Story/game/python-packages/winnie32api/notifs.py index e337ec9edb..04a71266b4 100644 --- a/Monika After Story/game/python-packages/winnie32api/notifs.py +++ b/Monika After Story/game/python-packages/winnie32api/notifs.py @@ -1,5 +1,6 @@ import ctypes import ctypes.wintypes as wt +import weakref from collections import deque from typing import Optional @@ -272,11 +273,18 @@ class IMAGE(): class MaxNotifsReachedError(Winnie32APIError): """ - An error raisedwhen spawned too many WindowsNotif + An error raised when spawned too many WindowsNotif """ def __str__(self) -> str: return "too many notification" +class InvalidNotifAccessError(Winnie32APIError): + """ + An error raised when tried to use a cleared WindowsNotif + """ + def __str__(self) -> str: + return "can't use notification after it's been cleared" + class WindowsNotif(): """ Class reprensets a windows notification @@ -337,12 +345,20 @@ def _deinit(self): self._unregister_win_cls() def __del__(self): - self._deinit() + """ + Cleanup on gc + """ if self._notif_id is not None: + self._deinit() self._NOTIF_ID_POOL.append(self._notif_id) self._notif_id = None def __call__(self): + """ + Shortcut for the send method + """ + if self._notif_id is None: + raise InvalidNotifAccessError() if not self._used: self._used = True self._display_notif() @@ -355,12 +371,21 @@ def send(self): def reset(self): """ - Resets this notifs allowing it to be send again + Resets this notif allowing it to be send again """ + if self._notif_id is None: + raise InvalidNotifAccessError() self._deinit() self._after_init() self._used = False + def clear(self): + """ + Clears this notif freeing the resources + The notif can't be used anymore after it's been cleared + """ + self.__del__() + def _load_icon(self): """ Loads the notification icon @@ -435,8 +460,8 @@ def _create_win(self): 0, self._cls_atom, # self._win_cls.lpszClassName, - self._app_name, - # self._win_cls.lpszClassName, + # self._app_name, + self._win_cls.lpszClassName, win_style, CW_USEDEFAULT, CW_USEDEFAULT, @@ -504,6 +529,8 @@ def __init__(self, app_name: str, icon_path: Optional[str]): self._app_name = app_name self._icon_path = icon_path + self._notifs: deque[weakref.ReferenceType[WindowsNotif]] = deque(maxlen=NOTIFS_LIMIT) + def spawn(self, title: str, body: str) -> WindowsNotif: """ Spawns a notif, but doesn't send it @@ -512,7 +539,9 @@ def spawn(self, title: str, body: str) -> WindowsNotif: title - the title of the notification body - the body of the notification """ - return WindowsNotif(self._app_name, self._icon_path, title, body) + notif = WindowsNotif(self._app_name, self._icon_path, title, body) + self._notifs.append(weakref.ref(notif)) + return notif def send(self, title: str, body: str) -> WindowsNotif: """ @@ -525,3 +554,13 @@ def send(self, title: str, body: str) -> WindowsNotif: notif = self.spawn(title, body) notif.send() return notif + + def clear(self): + """ + Clears all notification this manager has access to + """ + for notif_ref in self._notifs: + notif = notif_ref() + if notif: + notif.clear() + self._notifs.clear() diff --git a/Monika After Story/game/python-packages/winnie32api/windows.py b/Monika After Story/game/python-packages/winnie32api/windows.py index 6af8acc19a..07c17c8550 100644 --- a/Monika After Story/game/python-packages/winnie32api/windows.py +++ b/Monika After Story/game/python-packages/winnie32api/windows.py @@ -95,7 +95,7 @@ def get_window_rect(hwnd: HWND) -> Rect: if not result: raise WinAPIError("failed to get window rect", _get_last_err()) - return Rect.from_coords(c_rect.top, c_rect.left, c_rect.bottom, c_rect.right) + return Rect.from_coords(c_rect.left, c_rect.top, c_rect.right, c_rect.bottom) def get_active_window_hwnd() -> Optional[HWND]: From a114cb1993946fcd11ae50af253ea6d30f0f293b Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 19:09:49 +0300 Subject: [PATCH 134/180] migrate wr to py3, some cleanup --- Monika After Story/game/script-ch30.rpy | 2 +- Monika After Story/game/zz_windowutils.rpy | 129 +++++++++------------ 2 files changed, 57 insertions(+), 74 deletions(-) diff --git a/Monika After Story/game/script-ch30.rpy b/Monika After Story/game/script-ch30.rpy index d638d4fe5c..f0e0e069a5 100644 --- a/Monika After Story/game/script-ch30.rpy +++ b/Monika After Story/game/script-ch30.rpy @@ -2100,7 +2100,7 @@ label ch30_reset: $ mas_stripEVL("mas_after_bath_cleanup", list_pop=True, remove_dates=True) #set MAS window global - $ mas_windowutils._setMASWindow() + $ mas_windowutils._setMASWindow_Linux() # Did Monika make any progress on the islands? $ store.mas_island_event.advanceProgression() diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 01a859c1bd..af91effa9d 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -41,9 +41,16 @@ init python in mas_windowutils: import store #The initial setup + ## Linux # The window object, used on Linux systems, otherwise always None MAS_WINDOW = None + ## Windows + # The notification manager + WIN_NOTIF_MANAGER = None + # All current notifs, + win_notif_list = [] + #We can only do this on windows if renpy.windows: #We need to extend the sys path to see our packages @@ -52,30 +59,21 @@ init python in mas_windowutils: #We try/catch/except to make sure the game can run if load fails here try: - #Going to import win32gui for use in destroying notifs - import win32gui - #Import win32api so we know if we can or cannot use notifs - import win32api - - #Since importing the required libs was successful, we can move onto importing and initializing a balloontip - import balloontip - - #And finally, import the internal functions to make getting window handle easier - from win32gui import GetWindowText, GetForegroundWindow + import winnie32api #Now we initialize the notification class - __tip = balloontip.WindowsBalloonTip() - - #Now we set the hwnd of this temporarily - __tip.hwnd = None + WIN_NOTIF_MANAGER = winnie32api.WindowsNotifManager( + renpy.config.name, + None + ) - except: + except Exception: #If we fail to import, then we're going to have to make sure nothing can run. store.mas_windowreacts.can_show_notifs = False store.mas_windowreacts.can_do_windowreacts = False #Log this - store.mas_utils.mas_log.warning("win32api/win32gui failed to be imported, disabling notifications.") + store.mas_utils.mas_log.warning("winnie32api failed to be imported, disabling notifications.") elif renpy.linux: #Get session type @@ -98,7 +96,7 @@ init python in mas_windowutils: __display = Display() __root = __display.screen().root - except: + except Exception: store.mas_windowreacts.can_show_notifs = False store.mas_windowreacts.can_do_windowreacts = False @@ -114,24 +112,13 @@ init python in mas_windowutils: store.mas_windowreacts.can_do_windowreacts = False - class MASWindowFoundException(Exception): - """ - Custom exception class to flag a window found during a window enum - - Has the hwnd as a property - """ - def __init__(self, hwnd): - self.hwnd = hwnd - - def __str__(self): - return self.hwnd - #Fallback Const Defintion DEF_MOUSE_POS_RETURN = (0, 0) + ##Now, we start defining OS specific functions which we can set to a var for proper cross platform on a single func #Firstly, the internal helper functions - def __getActiveWindowObj_Linux(): + def __getActiveWindow_Linux(): """ Gets the active window object @@ -157,7 +144,7 @@ init python in mas_windowutils: except Xlib.error.XError: return None - def __getMASWindowLinux(): + def __getMASWindow_Linux(): """ Funtion to get the MAS window on Linux systems @@ -186,7 +173,7 @@ init python in mas_windowutils: except BadWindow: return None - def __getMASWindowHWND(): + def __getMASWindowHWND_Windows(): """ Gets the hWnd of the MAS window @@ -195,24 +182,20 @@ init python in mas_windowutils: OUT: int - represents the hWnd of the MAS window """ + hwnd = None #Verify we can actually do this before doing anything if not store.mas_windowreacts.can_do_windowreacts: - return None - - def checkMASWindow(hwnd, lParam): - """ - Internal function to identify the MAS window. Raises an exception when found to allow the main func to return - """ - if store.mas_getWindowTitle() == win32gui.GetWindowText(hwnd): - raise MASWindowFoundException(hwnd) + return hwnd try: - win32gui.EnumWindows(checkMASWindow, None) + # TODO: consider caching this + hwnd = winnie32api.get_hwnd_by_title(store.mas_getWindowTitle()) + except Exception: + pass - except MASWindowFoundException as e: - return e.hwnd + return hwnd - def __getAbsoluteGeometry(win): + def __getAbsoluteGeometry_Linux(win): """ Returns the (x, y, height, width) of a window relative to the top-left of the screen. @@ -226,7 +209,7 @@ init python in mas_windowutils: #If win is None, then we should just return a None here if win is None: # This handles some odd issues with setting window on Linux - win = _setMASWindow() + win = _setMASWindow_Linux() if win is None: return None @@ -247,10 +230,10 @@ init python in mas_windowutils: except Xlib.error.BadDrawable: #In the case of a bad drawable, we'll try to re-get the MAS window to get a good one - _setMASWindow() + _setMASWindow_Linux() return None - def _setMASWindow(): + def _setMASWindow_Linux(): """ Sets the MAS_WINDOW global on Linux systems @@ -260,7 +243,7 @@ init python in mas_windowutils: global MAS_WINDOW if renpy.linux: - MAS_WINDOW = __getMASWindowLinux() + MAS_WINDOW = __getMASWindow_Linux() else: MAS_WINDOW = None @@ -268,7 +251,7 @@ init python in mas_windowutils: return MAS_WINDOW #Next, the active window handle getters - def _getActiveWindowHandle_Windows(): + def _getActiveWindowHandle_Windows() -> str: """ Funtion to get the active window on Windows systems @@ -277,7 +260,10 @@ init python in mas_windowutils: ASSUMES: OS IS WINDOWS (renpy.windows) """ - return unicode(GetWindowText(GetForegroundWindow())) + try: + return winnie32api.get_active_window_title() + except Exception: + return "" def _getActiveWindowHandle_Linux(): """ @@ -289,7 +275,7 @@ init python in mas_windowutils: ASSUMES: OS IS LINUX (renpy.linux) """ NET_WM_NAME = __display.intern_atom("_NET_WM_NAME") - active_winobj = __getActiveWindowObj_Linux() + active_winobj = __getActiveWindow_Linux() if active_winobj is None: return "" @@ -330,12 +316,13 @@ init python in mas_windowutils: OUT: bool. True if the notification was successfully sent, False otherwise """ - # The Windows way, notif_success is adjusted if need be - notif_success = __tip.showWindow(title, body) + try: + notif = WIN_NOTIF_MANAGER.send(title, body) + win_notif_list.append(notif) + return True - #We need the IDs of the notifs to delete them from the tray - store.destroy_list.append(__tip.hwnd) - return notif_success + except Exception: + return False def _tryShowNotification_Linux(title, body): """ @@ -383,8 +370,8 @@ init python in mas_windowutils: if store.mas_windowreacts.can_do_windowreacts: #Try except here because we may not have permissions to do so try: - cur_pos = win32gui.GetCursorPos() - except: + cur_pos = tuple(winnie32api.get_screen_mouse_pos()) + except Exception: cur_pos = DEF_MOUSE_POS_RETURN else: @@ -407,19 +394,22 @@ init python in mas_windowutils: OUT: tuple representing window geometry or None if the window's hWnd could not be found """ - hwnd = __getMASWindowHWND() + hwnd = __getMASWindowHWND_Windows() if hwnd is None: return None - rv = win32gui.GetWindowRect(hwnd) + try: + rect = winnie32api.get_window_rect(hwnd) + except Exception: + return None - # win32gui may return incorrect geometry (-32k seems to be the limit), + # Windows may return incorrect geometry (-32k seems to be the limit), # in this case we return None - if rv[0] <= -32000 and rv[1] <= -32000: + if rect.top_left.x <= -32000 and rv.top_left.y <= -32000: return None - return rv + return (rect.top_left.x, rect.top_left.y, rect.bottom_right.x, rect.bottom_right.y) def _getMASWindowPos_Linux(): """ @@ -428,7 +418,7 @@ init python in mas_windowutils: OUT: tuple representing (left, top, right, bottom) of the window bounds, or None if not possible to get """ - geom = __getAbsoluteGeometry(MAS_WINDOW) + geom = __getAbsoluteGeometry_Linux(MAS_WINDOW) if geom is not None: return ( @@ -598,8 +588,6 @@ init python: "Do you have a minute, [player]?", ] - #List of hwnd IDs to destroy - destroy_list = list() #START: Utility methods def mas_canCheckActiveWindow(): @@ -676,9 +664,6 @@ init python: return notif_success return False - #TODO: Remove this at some point | Alias for depreciation - display_notif = mas_display_notif - def mas_isFocused(): """ Checks if MAS is the focused window @@ -714,9 +699,8 @@ init python: Clears all tray icons (also action center on win10) """ if renpy.windows and store.mas_windowreacts.can_show_notifs: - for index in range(len(destroy_list)-1,-1,-1): - store.mas_windowutils.win32gui.DestroyWindow(destroy_list[index]) - destroy_list.pop(index) + mas_windowutils.WIN_NOTIF_MANAGER.clear() + mas_windowutils.win_notif_list.clear() def mas_checkForWindowReacts(): """ @@ -807,4 +791,3 @@ init python: ASSUMES: renpy.windows """ store.mas_clearNotifs() - store.mas_windowutils.win32gui.UnregisterClass(__tip.classAtom, __tip.hinst) From d3fdbf13ae09bd5fd6923922f9bce5b9368f0239 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 20:13:02 +0300 Subject: [PATCH 135/180] add icon + fix typo --- Monika After Story/game/zz_windowutils.rpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index af91effa9d..5b812877b3 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -64,7 +64,7 @@ init python in mas_windowutils: #Now we initialize the notification class WIN_NOTIF_MANAGER = winnie32api.WindowsNotifManager( renpy.config.name, - None + os.path.join(config.basedir, "CustomIconWindows.ico") ) except Exception: @@ -406,7 +406,7 @@ init python in mas_windowutils: # Windows may return incorrect geometry (-32k seems to be the limit), # in this case we return None - if rect.top_left.x <= -32000 and rv.top_left.y <= -32000: + if rect.top_left.x <= -32000 and rect.top_left.y <= -32000: return None return (rect.top_left.x, rect.top_left.y, rect.bottom_right.x, rect.bottom_right.y) From 8b74a91e25f14ef6802c403a0950c6c9244e5fd2 Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 20:26:41 +0300 Subject: [PATCH 136/180] improve trace screen + fix typo --- Monika After Story/game/0config.rpy | 2 ++ Monika After Story/game/zz_windowutils.rpy | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Monika After Story/game/0config.rpy b/Monika After Story/game/0config.rpy index 7df6505fa8..d12e7d8e5a 100644 --- a/Monika After Story/game/0config.rpy +++ b/Monika After Story/game/0config.rpy @@ -125,3 +125,5 @@ define config.window_show_transition = dissolve_textbox define config.window_hide_transition = dissolve_textbox define config.mouse_focus_clickthrough = True +init python: + config.per_frame_screens.append("_trace_screen") diff --git a/Monika After Story/game/zz_windowutils.rpy b/Monika After Story/game/zz_windowutils.rpy index 5b812877b3..2889bf5295 100644 --- a/Monika After Story/game/zz_windowutils.rpy +++ b/Monika After Story/game/zz_windowutils.rpy @@ -64,7 +64,7 @@ init python in mas_windowutils: #Now we initialize the notification class WIN_NOTIF_MANAGER = winnie32api.WindowsNotifManager( renpy.config.name, - os.path.join(config.basedir, "CustomIconWindows.ico") + os.path.join(renpy.config.basedir, "CustomIconWindows.ico") ) except Exception: From 78907a34acfbcf57b4f3bb6f4f36a6b6c17e9d5a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:05:27 +0300 Subject: [PATCH 137/180] exclude logs --- .gitignore | 1 + Monika After Story/game/options.rpy | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2d453c9485..9ea0cd5b96 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ traceback.txt errors.txt firstrun log +*.log #DDLC specific *.chr diff --git a/Monika After Story/game/options.rpy b/Monika After Story/game/options.rpy index d670965e64..700d21fe63 100644 --- a/Monika After Story/game/options.rpy +++ b/Monika After Story/game/options.rpy @@ -138,7 +138,7 @@ init python: build.classify("game/python-packages/**", "all") # Add README build.classify("README.html", "all") - # Add icons (NOTE: unused) + # Add icons build.classify("CustomIcon**.**", "all") # build.package(build.directory_name + "Mod", "zip", "all", description="DDLC Compatible Mod") @@ -151,6 +151,7 @@ init python: # build.classify("game/saves/**", None) # Remove logs build.classify("log/**", None) + build.classify("*.log", None) ## Files matching documentation patterns are duplicated in a mac app build, ## so they appear in both the app and the zip file. From 6c656c1e7231fee00973e0c300606a85bab3868c Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:05:32 +0300 Subject: [PATCH 138/180] Update project.json --- Monika After Story/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monika After Story/project.json b/Monika After Story/project.json index f137c53541..94ce277ad3 100644 --- a/Monika After Story/project.json +++ b/Monika After Story/project.json @@ -1 +1 @@ -{"build_update": true, "packages": ["DDLC Mod File", "DDLCMod", "", "source", "market"], "add_from": false, "force_recompile": true, "renamed_all": true, "android_build": "Release", "renamed_steam": true} \ No newline at end of file +{"build_update": false, "packages": ["DDLC Mod File", "DDLCMod", "", "source", "market"], "add_from": false, "force_recompile": true, "renamed_all": true, "android_build": "Release", "renamed_steam": true} \ No newline at end of file From f78afb1a31bcea09fd34f2ecd323e586e0deb55a Mon Sep 17 00:00:00 2001 From: Totally a booplicate <53382877+Booplicate@users.noreply.github.com> Date: Thu, 1 Sep 2022 22:01:44 +0300 Subject: [PATCH 139/180] update icons the old weren't used also apparently we still used the old DDLC icon --- Monika After Story/CustomIconMac.icns | Bin 342117 -> 0 bytes Monika After Story/game/0config.rpy | 2 +- .../mod_assets/mas_icon.ico} | Bin Monika After Story/game/options.rpy | 2 -- Monika After Story/game/zz_windowutils.rpy | 2 +- 5 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 Monika After Story/CustomIconMac.icns rename Monika After Story/{CustomIconWindows.ico => game/mod_assets/mas_icon.ico} (100%) diff --git a/Monika After Story/CustomIconMac.icns b/Monika After Story/CustomIconMac.icns deleted file mode 100644 index de2a10b2dff4fc6084fc8a6b06607266579c736b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342117 zcmb4s2SA(2nYP^4>n+*jl5EN*xj%QwrQW~yr{~f(z3-)L>Ta^@b*1-?&?kvvs=JLF z*v8nHZkwutKms8;5~3GLsM6H|wfUd%k-&c-= zC4ByK9L{bcB)dYPT(T!bLPQwagWmxrw3mc}A;N_A1cH7)L!P1!eYPQY&_{+>81k+l zF}90tH+r{P>Tf$pFcjKm^vA19Z|b8XQ)dJI5Ha5O`L2|eUMo&c^LX6E*Kxz<8|{}} zYK@KPb_L0nOgv4-@75Qz#=eprBE;2G6n8y2HdA>1e9<5YkU&7vC>@)bx6bJ8+klN9 z=fbQ;XI{|T1HkL^WYgfbu8z#P<|mgow+Y!=86F+BX~$PB^QPby@dcerORLs($A)vg z;_7H^LZtgF7(Wwua>p-Z#&yBj+WKlZ{;bcP8T$bI*k^^X;J%(tw*6b zd9z>EuO3s&#>eLuJKD8|Ig@pLW!Y|CTr+E0ntDu2D-%^H!67W81a5sP}jcn`02rB2O#0WfS*YBb2#V87S#s)gto|e&e!~ShPP~op#N((AieBc z+9E)1=rIs|O26<*?|bx={ebxX`}@CcPlCkoSK|>n-q-w(2E=H7@@vkyzu@B!k{3B2 zaFO5+zCkkG`8Cdg^7btX6Ifn&oP)1joG({WdMm$7X%CB}X5h&u8O}u_;9MdM=L+Fq z(0!l}id;mV!3)>|3Kie@DIo7=Kces14?@JXO@cXOFa0>kgx*QaW*?9Z;vK14fO+7v z&!|E9C$grRkzSo8#7XtqU9wI>+2jKlchfSnK6taSvBnE?;9Xr~4zZdMU7q$Ve&6dW4}I6d1KfENmq?p`wEAr<^fB??7>J6fe6Lr&5rR}Ei2cxCuTJnv_bMHgHwek9%5Th%NdBfbFXA5y3k+>*hX zq@))!V+9Y{zR}$vm0g16T0e8U_)eEFPc7yqA7sMrQELVOYh679=a1f)8!m}Uj89F7 z$}G)IiVKTN+o0p)`BJ<5M0m`h!>=EYiH?g)OwSaQTqtk4Cdbq;l)rvEksTJCy?nPx zqMVqwIX7)q;_=(v=q@Dh#~5*9u~=D z-MvPwZg_TS-ubXE{y0YTGrGBvsrl9Ai6N`QX?I{mCiG#785NOrhr{O7&s*(Q2m_f~ zb_F&<{!IZLnXFmn30dDDr0#)qOcR)p)whXOoqC2m2f~7DWS~;q-yym?x>PUiYc&v~ zRp**;qUXP2B;sEnQyc0&_1vO8ux$?o{T{D#-L|mpa=AS_qWY|F+p)U3w5%H$o6>5= zCMRYL^OfzY&oZ3%_|}3xw?{QPtv8!ZOKYx8cr|c~3Gss*PBH5ef&6B0w63$~0>gPK z2y*;hqo23}!C+np=yMp3K>AFFtgAyR7K=n;v9zO0)*+Az@bSB3GAT@PXLoOJcc(-o zmda!TDZ}AQ-z%4Ph(!v`g2`gFT1*QXg-F~XmGh+>JUr0RAr&d6EjFu#Z+^^TwOOVW zB1X!Q2&IqFN5d8y-^^GZH=9gwUY22Tw)B8RB9(~7mn>#H*oV(;Yb%U-k7a4BT_D{r zy5A<1PT5S1nTJPfYqsgGezY{%v?{4YDwcN2J4Fipvc+Pxtaz9F-bHy#o(Xtam4?xF zK-3IZ