Merge ~jugmac00/launchpad:revisionsstatusreport-attach-and-setlog-now-accept-file-object into launchpad:master

Proposed by Jürgen Gmach
Status: Merged
Approved by: Jürgen Gmach
Approved revision: d83f4fd5ba13e74e811535c6e78acc89346ef42d
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~jugmac00/launchpad:revisionsstatusreport-attach-and-setlog-now-accept-file-object
Merge into: launchpad:master
Diff against target: 179 lines (+87/-6)
3 files modified
lib/lp/code/interfaces/revisionstatus.py (+4/-2)
lib/lp/code/model/revisionstatus.py (+23/-3)
lib/lp/code/model/tests/test_revisionstatus.py (+60/-1)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+415871@code.launchpad.net

Commit message

RevisionStatusReport.attach/.setLog now also accept a file object

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

I'd consider changing `CIUpload` to use this in the same MP. Probably not a major issue in this case, but when doing refactorings like this in the past I've sometimes found that changing the calling code along with the underlying refactoring causes me to notice problems I'd otherwise have missed.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/code/interfaces/revisionstatus.py b/lib/lp/code/interfaces/revisionstatus.py
2index 90d4e02..8dcb2e4 100644
3--- a/lib/lp/code/interfaces/revisionstatus.py
4+++ b/lib/lp/code/interfaces/revisionstatus.py
5@@ -149,7 +149,8 @@ class IRevisionStatusReportEdit(Interface):
6 def setLog(log_data):
7 """Set a new log on an existing status report.
8
9- :param log_data: The contents (in bytes) of the log.
10+ :param log_data: The contents of the log, either as bytes or as a file
11+ object.
12 """
13
14 # XXX cjwatson 2022-01-14: artifact_type isn't currently exported, but
15@@ -166,7 +167,8 @@ class IRevisionStatusReportEdit(Interface):
16 def attach(name, data, artifact_type=RevisionStatusArtifactType.BINARY):
17 """Attach a new artifact to an existing status report.
18
19- :param data: The contents (in bytes) of the artifact.
20+ :param data: The contents of the artifact, either as bytes or as a file
21+ object.
22 :param artifact_type: The type of the artifact. This may currently
23 only be `RevisionStatusArtifactType.BINARY`, but more types may
24 be added in future.
25diff --git a/lib/lp/code/model/revisionstatus.py b/lib/lp/code/model/revisionstatus.py
26index 8ef5cba..1f74af3 100644
27--- a/lib/lp/code/model/revisionstatus.py
28+++ b/lib/lp/code/model/revisionstatus.py
29@@ -8,6 +8,7 @@ __all__ = [
30 ]
31
32 import io
33+import os
34
35 import pytz
36 from storm.locals import (
37@@ -90,9 +91,18 @@ class RevisionStatusReport(StormBase):
38 def setLog(self, log_data):
39 filename = '%s-%s.txt' % (self.title, self.commit_sha1)
40
41+ if isinstance(log_data, bytes):
42+ file = io.BytesIO(log_data)
43+ size = len(log_data)
44+ else:
45+ file = log_data
46+ file.seek(0, os.SEEK_END)
47+ size = file.tell()
48+ file.seek(0)
49+
50 lfa = getUtility(ILibraryFileAliasSet).create(
51- name=filename, size=len(log_data),
52- file=io.BytesIO(log_data), contentType='text/plain',
53+ name=filename, size=size,
54+ file=file, contentType='text/plain',
55 restricted=self.git_repository.private)
56
57 getUtility(IRevisionStatusArtifactSet).new(
58@@ -101,8 +111,18 @@ class RevisionStatusReport(StormBase):
59 def attach(self, name, data,
60 artifact_type=RevisionStatusArtifactType.BINARY):
61 """See `IRevisionStatusReport`."""
62+
63+ if isinstance(data, bytes):
64+ file = io.BytesIO(data)
65+ size = len(data)
66+ else:
67+ file = data
68+ file.seek(0, os.SEEK_END)
69+ size = file.tell()
70+ file.seek(0)
71+
72 lfa = getUtility(ILibraryFileAliasSet).create(
73- name=name, size=len(data), file=io.BytesIO(data),
74+ name=name, size=size, file=file,
75 contentType='application/octet-stream',
76 restricted=self.git_repository.private)
77 getUtility(IRevisionStatusArtifactSet).new(lfa, self, artifact_type)
78diff --git a/lib/lp/code/model/tests/test_revisionstatus.py b/lib/lp/code/model/tests/test_revisionstatus.py
79index 7b5ed39..33f9c3c 100644
80--- a/lib/lp/code/model/tests/test_revisionstatus.py
81+++ b/lib/lp/code/model/tests/test_revisionstatus.py
82@@ -4,9 +4,14 @@
83 """Tests for revision status reports and artifacts."""
84
85 import hashlib
86+from hashlib import sha1
87 import io
88+import os
89
90-from fixtures import FakeLogger
91+from fixtures import (
92+ FakeLogger,
93+ TempDir,
94+ )
95 import requests
96 from storm.store import Store
97 from testtools.matchers import (
98@@ -30,6 +35,7 @@ from lp.code.interfaces.revisionstatus import (
99 IRevisionStatusReportSet,
100 )
101 from lp.services.auth.enums import AccessTokenScope
102+from lp.services.osutils import write_file
103 from lp.services.webapp.authorization import check_permission
104 from lp.testing import (
105 anonymous_logged_in,
106@@ -37,6 +43,7 @@ from lp.testing import (
107 person_logged_in,
108 TestCaseWithFactory,
109 )
110+from lp.testing.dbuser import switch_dbuser
111 from lp.testing.layers import (
112 DatabaseFunctionalLayer,
113 LaunchpadFunctionalLayer,
114@@ -195,6 +202,32 @@ class TestRevisionStatusReportWebservice(TestCaseWithFactory):
115 def test_setLog_private(self):
116 self._test_setLog(private=True)
117
118+ def test_setLog_with_file_object(self):
119+ switch_dbuser("launchpad_main")
120+
121+ # create log file
122+ path = os.path.join(
123+ self.useFixture(TempDir()).path, "test", "build:0.log"
124+ )
125+ content = "some log content"
126+ write_file(path, content.encode("utf-8"))
127+
128+ report = self.factory.makeRevisionStatusReport(
129+ title="build:0",
130+ ci_build=self.factory.makeCIBuild(),
131+ )
132+
133+ with person_logged_in(report.creator):
134+ with open(path, "rb") as f:
135+ report.setLog(f)
136+
137+ artifacts = list(getUtility(
138+ IRevisionStatusArtifactSet).findByReport(report))
139+ self.assertEqual(
140+ artifacts[0].library_file.content.sha1,
141+ sha1(content.encode()).hexdigest()
142+ )
143+
144 def _test_attach(self, private):
145 requester = self.factory.makePerson()
146 with person_logged_in(requester):
147@@ -234,6 +267,32 @@ class TestRevisionStatusReportWebservice(TestCaseWithFactory):
148 def test_attach_private(self):
149 self._test_attach(private=True)
150
151+ def test_attach_with_file_object(self):
152+ switch_dbuser("launchpad_main")
153+
154+ # create text file
155+ path = os.path.join(
156+ self.useFixture(TempDir()).path, "test.md"
157+ )
158+ content = "some content"
159+ write_file(path, content.encode("utf-8"))
160+
161+ report = self.factory.makeRevisionStatusReport(
162+ title="build:0",
163+ ci_build=self.factory.makeCIBuild(),
164+ )
165+
166+ with person_logged_in(report.creator):
167+ with open(path, "rb") as f:
168+ report.attach("text", f)
169+
170+ artifacts = list(getUtility(
171+ IRevisionStatusArtifactSet).findByReport(report))
172+ self.assertEqual(
173+ artifacts[0].library_file.content.sha1,
174+ sha1(content.encode()).hexdigest()
175+ )
176+
177 def test_update(self):
178 report = self.factory.makeRevisionStatusReport(
179 result=RevisionStatusResult.FAILED)

Subscribers

People subscribed via source and target branches

to status/vote changes: