diff options
| -rw-r--r-- | CHANGELOG | 20 | ||||
| -rw-r--r-- | examples/complex/dup_and_replay.py | 2 | ||||
| -rw-r--r-- | mitmproxy/addonmanager.py | 5 | ||||
| -rw-r--r-- | mitmproxy/addons/view.py | 219 | ||||
| -rw-r--r-- | mitmproxy/tools/console/consoleaddons.py | 4 | ||||
| -rw-r--r-- | mitmproxy/tools/console/defaultkeys.py | 12 | ||||
| -rw-r--r-- | mitmproxy/tools/console/flowlist.py | 12 | ||||
| -rw-r--r-- | mitmproxy/tools/console/flowview.py | 2 | ||||
| -rw-r--r-- | mitmproxy/tools/console/statusbar.py | 4 | ||||
| -rw-r--r-- | mitmproxy/types.py | 4 | ||||
| -rw-r--r-- | release/README.md | 9 | ||||
| -rwxr-xr-x | release/cibuild.py | 54 | ||||
| -rw-r--r-- | test/mitmproxy/addons/test_view.py | 50 | ||||
| -rw-r--r-- | test/mitmproxy/test_command.py | 2 | ||||
| -rw-r--r-- | test/mitmproxy/test_types.py | 2 | ||||
| -rw-r--r-- | test/release/test_cibuild.py | 91 |
16 files changed, 330 insertions, 162 deletions
@@ -1,3 +1,23 @@ +15 June 2018: mitmproxy 4.0.3 + + * Add support for IPv6 transparent mode on Windows (#3174) + * Add Docker images for ARMv7 - Raspberry Pi (#3190) + * Major overhaul of our release workflow - you probably won't notice it, but for us it's a big thing! + * Fix the Python version detection on Python 3.5, we now show a more intuitive error message (#3188) + * Fix application shutdown on Windows (#3172) + * Fix IPv6 scope suffixes in block addon (#3164) + * Fix options update when added (#3157) + * Fix "Edit Flow" button in mitmweb (#3136) + +15 June 2018: mitmproxy 4.0.2 + * Skipped! + + +17 May 2018: mitmproxy 4.0.1 + + ** Bugfixes ** + * The previous release had a packaging issue, so we bumped it to v4.0.1 and re-released it. + * This contains no actual bugfixes or new features. 17 May 2018: mitmproxy 4.0 diff --git a/examples/complex/dup_and_replay.py b/examples/complex/dup_and_replay.py index adcebff3..3ad98dc5 100644 --- a/examples/complex/dup_and_replay.py +++ b/examples/complex/dup_and_replay.py @@ -9,6 +9,6 @@ def request(flow): # Only interactive tools have a view. If we have one, add a duplicate entry # for our flow. if "view" in ctx.master.addons: - ctx.master.commands.call("view.add", [flow]) + ctx.master.commands.call("view.flows.add", [flow]) flow.request.path = "/changed" ctx.master.commands.call("replay.client", [flow]) diff --git a/mitmproxy/addonmanager.py b/mitmproxy/addonmanager.py index 0b559293..645f3a93 100644 --- a/mitmproxy/addonmanager.py +++ b/mitmproxy/addonmanager.py @@ -123,9 +123,10 @@ class AddonManager: """ Remove all addons. """ - for i in self.chain: - self.remove(i) + for a in self.chain: + self.invoke_addon(a, "done") self.lookup = {} + self.chain = [] def get(self, name): """ diff --git a/mitmproxy/addons/view.py b/mitmproxy/addons/view.py index ae00e9d1..1c8bd0ce 100644 --- a/mitmproxy/addons/view.py +++ b/mitmproxy/addons/view.py @@ -166,12 +166,6 @@ class View(collections.Sequence): def store_count(self): return len(self._store) - def inbounds(self, index: int) -> bool: - """ - Is this 0 <= index < len(self) - """ - return 0 <= index < len(self) - def _rev(self, idx: int) -> int: """ Reverses an index, if needed @@ -219,7 +213,26 @@ class View(collections.Sequence): self._base_add(i) self.sig_view_refresh.send(self) - # API + """ View API """ + + # Focus + @command.command("view.focus.go") + def go(self, dst: int) -> None: + """ + Go to a specified offset. Positive offests are from the beginning of + the view, negative from the end of the view, so that 0 is the first + flow, -1 is the last flow. + """ + if len(self) == 0: + return + if dst < 0: + dst = len(self) + dst + if dst < 0: + dst = 0 + if dst > len(self) - 1: + dst = len(self) - 1 + self.focus.flow = self[dst] + @command.command("view.focus.next") def focus_next(self) -> None: """ @@ -238,6 +251,7 @@ class View(collections.Sequence): if self.inbounds(idx): self.focus.flow = self[idx] + # Order @command.command("view.order.options") def order_options(self) -> typing.Sequence[str]: """ @@ -245,34 +259,58 @@ class View(collections.Sequence): """ return list(sorted(self.orders.keys())) - @command.command("view.marked.toggle") - def toggle_marked(self) -> None: - """ - Toggle whether to show marked views only. - """ - self.show_marked = not self.show_marked - self._refilter() - - def set_reversed(self, value: bool): + @command.command("view.order.reverse") + def set_reversed(self, value: bool) -> None: self.order_reversed = value self.sig_view_refresh.send(self) - def set_order(self, order_key: typing.Callable): + @command.command("view.order.set") + def set_order(self, order: str) -> None: """ Sets the current view order. """ + if order not in self.orders: + raise exceptions.CommandError( + "Unknown flow order: %s" % order + ) + order_key = self.orders[order] self.order_key = order_key newview = sortedcontainers.SortedListWithKey(key=order_key) newview.update(self._view) self._view = newview - def set_filter(self, flt: typing.Optional[flowfilter.TFilter]): + @command.command("view.order") + def get_order(self) -> str: + """ + Returns the current view order. + """ + order = "" + for k in self.orders.keys(): + if self.order_key == self.orders[k]: + order = k + return order + + # Filter + @command.command("view.filter.set") + def set_filter_cmd(self, f: str) -> None: """ Sets the current view filter. """ + filt = None + if f: + filt = flowfilter.parse(f) + if not filt: + raise exceptions.CommandError( + "Invalid interception filter: %s" % f + ) + self.set_filter(filt) + + def set_filter(self, flt: typing.Optional[flowfilter.TFilter]): self.filter = flt or matchall self._refilter() + # View Updates + @command.command("view.clear") def clear(self) -> None: """ Clears both the store and view. @@ -282,7 +320,8 @@ class View(collections.Sequence): self.sig_view_refresh.send(self) self.sig_store_refresh.send(self) - def clear_not_marked(self): + @command.command("view.clear_unmarked") + def clear_not_marked(self) -> None: """ Clears only the unmarked flows. """ @@ -293,36 +332,15 @@ class View(collections.Sequence): self._refilter() self.sig_store_refresh.send(self) - @command.command("view.marked.toggle") - def add(self, flows: typing.Sequence[mitmproxy.flow.Flow]) -> None: - """ - Adds a flow to the state. If the flow already exists, it is - ignored. - """ - for f in flows: - if f.id not in self._store: - self._store[f.id] = f - if self.filter(f): - self._base_add(f) - if self.focus_follow: - self.focus.flow = f - self.sig_view_add.send(self, flow=f) - - def get_by_id(self, flow_id: str) -> typing.Optional[mitmproxy.flow.Flow]: - """ - Get flow with the given id from the store. - Returns None if the flow is not found. - """ - return self._store.get(flow_id) - - @command.command("view.getval") + # View Settings + @command.command("view.settings.getval") def getvalue(self, f: mitmproxy.flow.Flow, key: str, default: str) -> str: """ Get a value from the settings store for the specified flow. """ return self.settings[f].get(key, default) - @command.command("view.setval.toggle") + @command.command("view.settings.setval.toggle") def setvalue_toggle( self, flows: typing.Sequence[mitmproxy.flow.Flow], @@ -339,7 +357,7 @@ class View(collections.Sequence): updated.append(f) ctx.master.addons.trigger("update", updated) - @command.command("view.setval") + @command.command("view.settings.setval") def setvalue( self, flows: typing.Sequence[mitmproxy.flow.Flow], @@ -354,41 +372,8 @@ class View(collections.Sequence): updated.append(f) ctx.master.addons.trigger("update", updated) - @command.command("view.load") - def load_file(self, path: mitmproxy.types.Path) -> None: - """ - Load flows into the view, without processing them with addons. - """ - try: - with open(path, "rb") as f: - for i in io.FlowReader(f).stream(): - # Do this to get a new ID, so we can load the same file N times and - # get new flows each time. It would be more efficient to just have a - # .newid() method or something. - self.add([i.copy()]) - except IOError as e: - ctx.log.error(e.strerror) - except exceptions.FlowReadException as e: - ctx.log.error(str(e)) - - @command.command("view.go") - def go(self, dst: int) -> None: - """ - Go to a specified offset. Positive offests are from the beginning of - the view, negative from the end of the view, so that 0 is the first - flow, -1 is the last flow. - """ - if len(self) == 0: - return - if dst < 0: - dst = len(self) + dst - if dst < 0: - dst = 0 - if dst > len(self) - 1: - dst = len(self) - 1 - self.focus.flow = self[dst] - - @command.command("view.duplicate") + # Flows + @command.command("view.flows.duplicate") def duplicate(self, flows: typing.Sequence[mitmproxy.flow.Flow]) -> None: """ Duplicates the specified flows, and sets the focus to the first @@ -400,7 +385,7 @@ class View(collections.Sequence): self.focus.flow = dups[0] ctx.log.alert("Duplicated %s flows" % len(dups)) - @command.command("view.remove") + @command.command("view.flows.remove") def remove(self, flows: typing.Sequence[mitmproxy.flow.Flow]) -> None: """ Removes the flow from the underlying store and the view. @@ -420,7 +405,7 @@ class View(collections.Sequence): if len(flows) > 1: ctx.log.alert("Removed %s flows" % len(flows)) - @command.command("view.resolve") + @command.command("view.flows.resolve") def resolve(self, spec: str) -> typing.Sequence[mitmproxy.flow.Flow]: """ Resolve a flow list specification to an actual list of flows. @@ -443,7 +428,7 @@ class View(collections.Sequence): raise exceptions.CommandError("Invalid flow filter: %s" % spec) return [i for i in self._store.values() if filt(i)] - @command.command("view.create") + @command.command("view.flows.create") def create(self, method: str, url: str) -> None: try: req = http.HTTPRequest.make(method.upper(), url) @@ -456,6 +441,74 @@ class View(collections.Sequence): f.request.headers["Host"] = req.host self.add([f]) + @command.command("view.flows.load") + def load_file(self, path: mitmproxy.types.Path) -> None: + """ + Load flows into the view, without processing them with addons. + """ + try: + with open(path, "rb") as f: + for i in io.FlowReader(f).stream(): + # Do this to get a new ID, so we can load the same file N times and + # get new flows each time. It would be more efficient to just have a + # .newid() method or something. + self.add([i.copy()]) + except IOError as e: + ctx.log.error(e.strerror) + except exceptions.FlowReadException as e: + ctx.log.error(str(e)) + + def add(self, flows: typing.Sequence[mitmproxy.flow.Flow]) -> None: + """ + Adds a flow to the state. If the flow already exists, it is + ignored. + """ + for f in flows: + if f.id not in self._store: + self._store[f.id] = f + if self.filter(f): + self._base_add(f) + if self.focus_follow: + self.focus.flow = f + self.sig_view_add.send(self, flow=f) + + def get_by_id(self, flow_id: str) -> typing.Optional[mitmproxy.flow.Flow]: + """ + Get flow with the given id from the store. + Returns None if the flow is not found. + """ + return self._store.get(flow_id) + + # View Properties + @command.command("view.properties.length") + def get_length(self) -> int: + """ + Returns view length. + """ + return len(self) + + @command.command("view.properties.marked") + def get_marked(self) -> bool: + """ + Returns true if view is in marked mode. + """ + return self.show_marked + + @command.command("view.properties.marked.toggle") + def toggle_marked(self) -> None: + """ + Toggle whether to show marked views only. + """ + self.show_marked = not self.show_marked + self._refilter() + + @command.command("view.properties.inbounds") + def inbounds(self, index: int) -> bool: + """ + Is this 0 <= index < len(self)? + """ + return 0 <= index < len(self) + # Event handlers def configure(self, updated): if "view_filter" in updated: @@ -472,7 +525,7 @@ class View(collections.Sequence): raise exceptions.OptionsError( "Unknown flow order: %s" % ctx.options.view_order ) - self.set_order(self.orders[ctx.options.view_order]) + self.set_order(ctx.options.view_order) if "view_order_reversed" in updated: self.set_reversed(ctx.options.view_order_reversed) if "console_focus_follow" in updated: diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py index 54fe11c4..a40cdeaa 100644 --- a/mitmproxy/tools/console/consoleaddons.py +++ b/mitmproxy/tools/console/consoleaddons.py @@ -515,7 +515,7 @@ class ConsoleAddon: try: self.master.commands.call_strings( - "view.setval", + "view.settings.setval", ["@focus", "flowview_mode_%s" % idx, mode] ) except exceptions.CommandError as e: @@ -538,7 +538,7 @@ class ConsoleAddon: raise exceptions.CommandError("Not viewing a flow.") idx = fv.body.tab_offset return self.master.commands.call_strings( - "view.getval", + "view.settings.getval", [ "@focus", "flowview_mode_%s" % idx, diff --git a/mitmproxy/tools/console/defaultkeys.py b/mitmproxy/tools/console/defaultkeys.py index 7f65c1f7..0f2e9072 100644 --- a/mitmproxy/tools/console/defaultkeys.py +++ b/mitmproxy/tools/console/defaultkeys.py @@ -36,8 +36,8 @@ def map(km): ["flowlist", "flowview"], "Save response body to file" ) - km.add("d", "view.remove @focus", ["flowlist", "flowview"], "Delete flow from view") - km.add("D", "view.duplicate @focus", ["flowlist", "flowview"], "Duplicate flow") + km.add("d", "view.flows.remove @focus", ["flowlist", "flowview"], "Delete flow from view") + km.add("D", "view.flows.duplicate @focus", ["flowlist", "flowview"], "Duplicate flow") km.add( "e", """ @@ -57,7 +57,7 @@ def map(km): ) km.add("L", "console.command view.load ", ["flowlist"], "Load flows from file") km.add("m", "flow.mark.toggle @focus", ["flowlist"], "Toggle mark on this flow") - km.add("M", "view.marked.toggle", ["flowlist"], "Toggle viewing marked flows") + km.add("M", "view.properties.marked.toggle", ["flowlist"], "Toggle viewing marked flows") km.add( "n", "console.command view.create get https://example.com/", @@ -80,8 +80,8 @@ def map(km): km.add("w", "console.command save.file @shown ", ["flowlist"], "Save listed flows to file") km.add("V", "flow.revert @focus", ["flowlist", "flowview"], "Revert changes to this flow") km.add("X", "flow.kill @focus", ["flowlist"], "Kill this flow") - km.add("z", "view.remove @all", ["flowlist"], "Clear flow list") - km.add("Z", "view.remove @hidden", ["flowlist"], "Purge all flows not showing") + km.add("z", "view.flows.remove @all", ["flowlist"], "Clear flow list") + km.add("Z", "view.flows.remove @hidden", ["flowlist"], "Purge all flows not showing") km.add( "|", "console.command script.run @focus ", @@ -100,7 +100,7 @@ def map(km): ) km.add( "f", - "view.setval.toggle @focus fullcontents", + "view.settings.setval.toggle @focus fullcontents", ["flowview"], "Toggle viewing full contents on this flow", ) diff --git a/mitmproxy/tools/console/flowlist.py b/mitmproxy/tools/console/flowlist.py index a9e48af4..e947a582 100644 --- a/mitmproxy/tools/console/flowlist.py +++ b/mitmproxy/tools/console/flowlist.py @@ -42,7 +42,7 @@ class FlowListWalker(urwid.ListWalker): def positions(self, reverse=False): # The stub implementation of positions can go once this issue is resolved: # https://github.com/urwid/urwid/issues/294 - ret = range(len(self.master.view)) + ret = range(self.master.commands.execute("view.properties.length")) if reverse: return reversed(ret) return ret @@ -57,19 +57,19 @@ class FlowListWalker(urwid.ListWalker): return f, self.master.view.focus.index def set_focus(self, index): - if self.master.view.inbounds(index): + if self.master.commands.execute("view.properties.inbounds %d" % index): self.master.view.focus.index = index def get_next(self, pos): pos = pos + 1 - if not self.master.view.inbounds(pos): + if not self.master.commands.execute("view.properties.inbounds %d" % pos): return None, None f = FlowItem(self.master, self.master.view[pos]) return f, pos def get_prev(self, pos): pos = pos - 1 - if not self.master.view.inbounds(pos): + if not self.master.commands.execute("view.properties.inbounds %d" % pos): return None, None f = FlowItem(self.master, self.master.view[pos]) return f, pos @@ -87,9 +87,9 @@ class FlowListBox(urwid.ListBox, layoutwidget.LayoutWidget): def keypress(self, size, key): if key == "m_start": - self.master.commands.execute("view.go 0") + self.master.commands.execute("view.focus.go 0") elif key == "m_end": - self.master.commands.execute("view.go -1") + self.master.commands.execute("view.focus.go -1") elif key == "m_select": self.master.commands.execute("console.view.flow @focus") return urwid.ListBox.keypress(self, size, key) diff --git a/mitmproxy/tools/console/flowview.py b/mitmproxy/tools/console/flowview.py index b8ba7f12..87671c3b 100644 --- a/mitmproxy/tools/console/flowview.py +++ b/mitmproxy/tools/console/flowview.py @@ -98,7 +98,7 @@ class FlowDetails(tabs.Tabs): msg, body = "", [urwid.Text([("error", "[content missing]")])] return msg, body else: - full = self.master.commands.execute("view.getval @focus fullcontents false") + full = self.master.commands.execute("view.settings.getval @focus fullcontents false") if full == "true": limit = sys.maxsize else: diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index ccf5e2e0..215cf500 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -271,7 +271,7 @@ class StatusBar(urwid.WidgetWrap): return r def redraw(self): - fc = len(self.master.view) + fc = self.master.commands.execute("view.properties.length") if self.master.view.focus.flow is None: offset = 0 else: @@ -283,7 +283,7 @@ class StatusBar(urwid.WidgetWrap): arrow = common.SYMBOL_DOWN marked = "" - if self.master.view.show_marked: + if self.master.commands.execute("view.properties.marked"): marked = "M" t = [ diff --git a/mitmproxy/types.py b/mitmproxy/types.py index 283e7e2e..f2a26b40 100644 --- a/mitmproxy/types.py +++ b/mitmproxy/types.py @@ -337,7 +337,7 @@ class _FlowType(_BaseFlowType): def parse(self, manager: _CommandBase, t: type, s: str) -> flow.Flow: try: - flows = manager.call_strings("view.resolve", [s]) + flows = manager.call_strings("view.flows.resolve", [s]) except exceptions.CommandError as e: raise exceptions.TypeError from e if len(flows) != 1: @@ -356,7 +356,7 @@ class _FlowsType(_BaseFlowType): def parse(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[flow.Flow]: try: - return manager.call_strings("view.resolve", [s]) + return manager.call_strings("view.flows.resolve", [s]) except exceptions.CommandError as e: raise exceptions.TypeError from e diff --git a/release/README.md b/release/README.md index 01a8d7dd..6f4e8f1a 100644 --- a/release/README.md +++ b/release/README.md @@ -7,14 +7,11 @@ release for! The command examples assume that you have a git remote called - Verify that `mitmproxy/version.py` is correct - Update CHANGELOG - Verify that all CI tests pass -- Create a major version branch - e.g. `v4.x`. Assuming you have a remote repo called `upstream` that points to the mitmproxy/mitmproxy repo:: +- If needed, create a major version branch - e.g. `v4.x`. Assuming you have a remote repo called `upstream` that points to the mitmproxy/mitmproxy repo:: - `git checkout -b v4.x upstream/master` - `git push -u upstream v4.x` - Tag the release and push to Github - - For alphas, betas, and release candidates, use lightweight tags. This is - necessary so that the .devXXXX counter does not reset. - - For final releases, use annotated tags. This makes the .devXXXX counter reset. - - `git tag -a v4.0.0 -m v4.0.0` + - `git tag v4.0.0` - `git push upstream v4.0.0` - Wait for tag CI to complete @@ -39,7 +36,7 @@ release for! The command examples assume that you have a git remote called ## Docker - The docker image is built on Travis and pushed to Docker Hub automatically. - Please check https://hub.docker.com/r/mitmproxy/mitmproxy/tags/ about the latest version -- Update `latest` tag: `docker tag mitmproxy/mitmproxy:<version number here> mitmproxy/mitmproxy:latest && docker push mitmproxy/mitmproxy:latest` +- Update `latest` tag: `export VERSION=4.0.3 && docker pull mitmproxy/mitmproxy:$VERSION && docker tag mitmproxy/mitmproxy:$VERSION mitmproxy/mitmproxy:latest && docker push mitmproxy/mitmproxy:latest` ## Website - Update version here: diff --git a/release/cibuild.py b/release/cibuild.py index 2d11d69a..37511086 100755 --- a/release/cibuild.py +++ b/release/cibuild.py @@ -63,6 +63,12 @@ class BuildEnviron: self.travis_tag = travis_tag self.travis_branch = travis_branch + + if travis_tag and travis_tag != travis_branch: + raise ValueError( + f"Something is wrong - TRAVIS_TAG={travis_tag}, but TRAVIS_BRANCH={travis_branch}" + ) + self.travis_pull_request = travis_pull_request self.should_build_wheel = should_build_wheel @@ -184,25 +190,29 @@ class BuildEnviron: """ with open(pathlib.Path(self.root_dir) / "mitmproxy" / "version.py") as f: contents = f.read() - version = re.search(r'^VERSION = "(.+?)"', contents, re.M).group(1) - if self.tag: - # For (tagged) releases, we are strict: - # 1. The tagname must match the version in mitmproxy/version.py - # 2. The version info must be in canonical form (as recommended in PEP 440). - - if version != self.tag: + if self.is_prod_release: + # For production releases, we require strict version equality + if self.version != version: raise ValueError(f"Tag is {self.tag}, but mitmproxy/version.py is {version}.") - try: - parver.Version.parse(version, strict=True) - except parver.ParseError as e: - raise ValueError(str(e)) from e - else: - # For snapshots, we only ensure that mitmproxy/version.py contains a dev release. + elif not self.is_maintenance_branch: + # Commits on maintenance branches don't need the dev suffix. This + # allows us to incorporate and test commits between tagged releases. + # For snapshots, we only ensure that mitmproxy/version.py contains a + # dev release. version_info = parver.Version.parse(version) if not version_info.is_devrelease: - raise ValueError("Releases must be tagged.") + raise ValueError(f"Non-production releases must have dev suffix: {version}") + + @property + def is_maintenance_branch(self) -> bool: + """ + Is this an untagged commit on a maintenance branch? + """ + if not self.tag and self.branch and re.match(r"v\d+\.x", self.branch): + return True + return False @property def has_docker_creds(self) -> bool: @@ -210,7 +220,7 @@ class BuildEnviron: @property def is_prod_release(self) -> bool: - if not self.tag: + if not (self.tag and self.tag.startswith("v")): return False try: v = parver.Version.parse(self.version, strict=True) @@ -265,10 +275,18 @@ class BuildEnviron: @property def version(self): - name = self.tag or self.branch - if not name: + if self.tag: + if self.tag.startswith("v"): + try: + parver.Version.parse(self.tag[1:], strict=True) + except parver.ParseError as e: + return self.tag + return self.tag[1:] + return self.tag + elif self.branch: + return self.branch + else: raise BuildError("We're on neither a tag nor a branch - could not establish version") - return name def build_wheel(be: BuildEnviron): # pragma: no cover diff --git a/test/mitmproxy/addons/test_view.py b/test/mitmproxy/addons/test_view.py index bd724950..976c14b7 100644 --- a/test/mitmproxy/addons/test_view.py +++ b/test/mitmproxy/addons/test_view.py @@ -107,13 +107,12 @@ def test_simple(): def test_filter(): v = view.View() - f = flowfilter.parse("~m get") v.request(tft(method="get")) v.request(tft(method="put")) v.request(tft(method="get")) v.request(tft(method="put")) assert(len(v)) == 4 - v.set_filter(f) + v.set_filter_cmd("~m get") assert [i.request.method for i in v] == ["GET", "GET"] assert len(v._store) == 4 v.set_filter(None) @@ -124,6 +123,9 @@ def test_filter(): v.toggle_marked() assert len(v) == 4 + with pytest.raises(exceptions.CommandError): + v.set_filter_cmd("~notafilter regex") + v[1].marked = True v.toggle_marked() assert len(v) == 1 @@ -303,23 +305,26 @@ def test_setgetval(): def test_order(): v = view.View() - with taddons.context(v) as tctx: - v.request(tft(method="get", start=1)) - v.request(tft(method="put", start=2)) - v.request(tft(method="get", start=3)) - v.request(tft(method="put", start=4)) - assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] - - tctx.configure(v, view_order="method") - assert [i.request.method for i in v] == ["GET", "GET", "PUT", "PUT"] - v.set_reversed(True) - assert [i.request.method for i in v] == ["PUT", "PUT", "GET", "GET"] + v.request(tft(method="get", start=1)) + v.request(tft(method="put", start=2)) + v.request(tft(method="get", start=3)) + v.request(tft(method="put", start=4)) + assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] + + v.set_order("method") + assert v.get_order() == "method" + assert [i.request.method for i in v] == ["GET", "GET", "PUT", "PUT"] + v.set_reversed(True) + assert [i.request.method for i in v] == ["PUT", "PUT", "GET", "GET"] - tctx.configure(v, view_order="time") - assert [i.request.timestamp_start for i in v] == [4, 3, 2, 1] + v.set_order("time") + assert v.get_order() == "time" + assert [i.request.timestamp_start for i in v] == [4, 3, 2, 1] - v.set_reversed(False) - assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] + v.set_reversed(False) + assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4] + with pytest.raises(exceptions.CommandError): + v.set_order("not_an_order") def test_reversed(): @@ -551,6 +556,17 @@ def test_settings(): assert not v.settings.keys() +def test_properties(): + v = view.View() + f = tft() + v.request(f) + assert v.get_length() == 1 + assert not v.get_marked() + v.toggle_marked() + assert v.get_length() == 0 + assert v.get_marked() + + def test_configure(): v = view.View() with taddons.context(v) as tctx: diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py index 029dbafd..d9dcf5f9 100644 --- a/test/mitmproxy/test_command.py +++ b/test/mitmproxy/test_command.py @@ -313,7 +313,7 @@ def test_typename(): class DummyConsole: - @command.command("view.resolve") + @command.command("view.flows.resolve") def resolve(self, spec: str) -> typing.Sequence[flow.Flow]: n = int(spec) return [tflow.tflow(resp=True)] * n diff --git a/test/mitmproxy/test_types.py b/test/mitmproxy/test_types.py index 35ff3241..571985fb 100644 --- a/test/mitmproxy/test_types.py +++ b/test/mitmproxy/test_types.py @@ -146,7 +146,7 @@ def test_strseq(): class DummyConsole: - @command.command("view.resolve") + @command.command("view.flows.resolve") def resolve(self, spec: str) -> typing.Sequence[flow.Flow]: if spec == "err": raise mitmproxy.exceptions.CommandError() diff --git a/test/release/test_cibuild.py b/test/release/test_cibuild.py index efa2f072..cfa24e63 100644 --- a/test/release/test_cibuild.py +++ b/test/release/test_cibuild.py @@ -36,6 +36,14 @@ def test_buildenviron_common(): with pytest.raises(cibuild.BuildError): be.platform_tag + with pytest.raises(ValueError, match="TRAVIS_TAG"): + be = cibuild.BuildEnviron( + system="Linux", + root_dir="/foo", + travis_tag="one", + travis_branch="two", + ) + def test_buildenviron_pr(): # Simulates a PR. We build everything, but don't have access to secret @@ -56,6 +64,7 @@ def test_buildenviron_pr(): ) assert be.is_pull_request assert not be.is_prod_release + assert not be.is_maintenance_branch def test_buildenviron_commit(): @@ -75,6 +84,7 @@ def test_buildenviron_commit(): assert not be.should_upload_pypi assert be.should_upload_docker assert not be.is_prod_release + assert not be.is_maintenance_branch def test_buildenviron_releasetag(): @@ -82,8 +92,8 @@ def test_buildenviron_releasetag(): be = cibuild.BuildEnviron( system="Linux", root_dir="/foo", - travis_tag="0.0.1", - travis_branch="v0.x", + travis_tag="v0.0.1", + travis_branch="v0.0.1", should_build_wheel=True, should_build_docker=True, should_build_pyinstaller=True, @@ -91,18 +101,44 @@ def test_buildenviron_releasetag(): docker_username="foo", docker_password="bar", ) - assert be.tag == "0.0.1" - assert be.branch == "v0.x" + assert be.tag == "v0.0.1" + assert be.branch == "v0.0.1" assert be.version == "0.0.1" assert be.upload_dir == "0.0.1" assert be.docker_tag == "mitmproxy/mitmproxy:0.0.1" assert be.should_upload_pypi assert be.should_upload_docker assert be.is_prod_release + assert not be.is_maintenance_branch + + +def test_buildenviron_namedtag(): + # Simulates a non-release tag on a branch. + be = cibuild.BuildEnviron( + system="Linux", + root_dir="/foo", + travis_tag="anyname", + travis_branch="anyname", + should_build_wheel=True, + should_build_docker=True, + should_build_pyinstaller=True, + has_twine_creds=True, + docker_username="foo", + docker_password="bar", + ) + assert be.tag == "anyname" + assert be.branch == "anyname" + assert be.version == "anyname" + assert be.upload_dir == "anyname" + assert be.docker_tag == "mitmproxy/mitmproxy:anyname" + assert not be.should_upload_pypi + assert not be.should_upload_docker + assert not be.is_prod_release + assert not be.is_maintenance_branch -def test_buildenviron_branch(): - # Simulates a development branch on the main repo +def test_buildenviron_dev_branch(): + # Simulates a commit on a development branch on the main repo be = cibuild.BuildEnviron( system="Linux", root_dir="/foo", @@ -121,6 +157,30 @@ def test_buildenviron_branch(): assert be.upload_dir == "branches/mybranch" assert not be.should_upload_pypi assert not be.should_upload_docker + assert not be.is_maintenance_branch + + +def test_buildenviron_maintenance_branch(): + # Simulates a commit on a release maintenance branch on the main repo + be = cibuild.BuildEnviron( + system="Linux", + root_dir="/foo", + travis_tag="", + travis_branch="v0.x", + should_build_wheel=True, + should_build_docker=True, + should_build_pyinstaller=True, + has_twine_creds=True, + docker_username="foo", + docker_password="bar", + ) + assert be.tag == "" + assert be.branch == "v0.x" + assert be.version == "v0.x" + assert be.upload_dir == "branches/v0.x" + assert not be.should_upload_pypi + assert not be.should_upload_docker + assert be.is_maintenance_branch def test_buildenviron_osx(tmpdir): @@ -128,7 +188,7 @@ def test_buildenviron_osx(tmpdir): system="Darwin", root_dir="/foo", travis_tag="0.0.1", - travis_branch="v0.x", + travis_branch="0.0.1", ) assert be.platform_tag == "osx" assert be.bdists == { @@ -146,8 +206,8 @@ def test_buildenviron_windows(tmpdir): be = cibuild.BuildEnviron( system="Windows", root_dir="/foo", - travis_tag="0.0.1", - travis_branch="v0.x", + travis_tag="v0.0.1", + travis_branch="v0.0.1", ) assert be.platform_tag == "windows" assert be.bdists == { @@ -163,18 +223,21 @@ def test_buildenviron_windows(tmpdir): @pytest.mark.parametrize("version, tag, ok", [ ("3.0.0.dev", "", True), # regular snapshot - ("3.0.0.dev", "3.0.0", False), # forgot to remove ".dev" on bump + ("3.0.0.dev", "v3.0.0", False), # forgot to remove ".dev" on bump ("3.0.0", "", False), # forgot to re-add ".dev" - ("3.0.0", "4.0.0", False), # version mismatch - ("3.0.0", "3.0.0", True), # regular release - ("3.0.0.rc1", "3.0.0.rc1", False), # non-canonical. + ("3.0.0", "v4.0.0", False), # version mismatch + ("3.0.0", "v3.0.0", True), # regular release + ("3.0.0.rc1", "v3.0.0.rc1", False), # non-canonical. + ("3.0.0.dev", "anyname", True), # tagged test/dev release + ("3.0.0", "3.0.0", False), # tagged, but without v prefix ]) def test_buildenviron_check_version(version, tag, ok, tmpdir): tmpdir.mkdir("mitmproxy").join("version.py").write(f'VERSION = "{version}"') be = cibuild.BuildEnviron( root_dir=tmpdir, - travis_tag=tag + travis_tag=tag, + travis_branch=tag or "branch", ) if ok: be.check_version() |
