2
* Copyright 2011 Google Inc.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
17
// Author: jmarantz@google.com (Joshua Marantz)
19
// Unit-test the RewriteContext class. This is made simplest by
20
// setting up some dummy rewriters in our test framework.
22
#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_CONTEXT_TEST_BASE_H_
23
#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_CONTEXT_TEST_BASE_H_
25
#include "net/instaweb/rewriter/public/rewrite_context.h"
30
#include "net/instaweb/htmlparse/public/html_element.h"
31
#include "net/instaweb/htmlparse/public/html_name.h"
32
#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
33
#include "net/instaweb/http/public/content_type.h"
34
#include "net/instaweb/rewriter/public/output_resource_kind.h"
35
#include "net/instaweb/rewriter/public/resource.h"
36
#include "net/instaweb/rewriter/public/resource_combiner.h"
37
#include "net/instaweb/rewriter/public/resource_slot.h"
38
#include "net/instaweb/rewriter/public/rewrite_driver.h"
39
#include "net/instaweb/rewriter/public/rewrite_filter.h"
40
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
41
#include "net/instaweb/rewriter/public/server_context.h"
42
#include "net/instaweb/rewriter/public/simple_text_filter.h"
43
#include "net/instaweb/rewriter/public/single_rewrite_context.h"
44
#include "net/instaweb/util/public/basictypes.h"
45
#include "net/instaweb/util/public/scoped_ptr.h"
46
#include "net/instaweb/util/public/string.h"
47
#include "net/instaweb/util/public/string_util.h"
48
#include "net/instaweb/util/public/timer.h"
49
#include "net/instaweb/util/public/url_multipart_encoder.h"
50
#include "net/instaweb/util/public/writer.h"
51
#include "net/instaweb/util/worker_test_base.h"
54
namespace net_instaweb {
59
class OutputPartitions;
61
class TestRewriteDriverFactory;
62
class UrlSegmentEncoder;
64
// Simple test filter just trims whitespace from the input resource.
65
class TrimWhitespaceRewriter : public SimpleTextFilter::Rewriter {
67
static const char kFilterId[];
69
explicit TrimWhitespaceRewriter(OutputResourceKind kind) : kind_(kind) {
74
int num_rewrites() const { return num_rewrites_; }
75
void ClearStats() { num_rewrites_ = 0; }
78
REFCOUNT_FRIEND_DECLARATION(TrimWhitespaceRewriter);
79
virtual ~TrimWhitespaceRewriter();
81
virtual bool RewriteText(const StringPiece& url, const StringPiece& in,
83
ServerContext* server_context);
84
virtual HtmlElement::Attribute* FindResourceAttribute(HtmlElement* element);
85
virtual OutputResourceKind kind() const { return kind_; }
86
virtual const char* id() const { return kFilterId; }
87
virtual const char* name() const { return "TrimWhitespace"; }
90
OutputResourceKind kind_;
94
DISALLOW_COPY_AND_ASSIGN(TrimWhitespaceRewriter);
97
// Test filter that replaces a CSS resource URL with a corresponding Pagespeed
98
// resource URL. When that URL is requested, it will invoke a rewriter that
99
// trims whitespace in the line of serving. Does not require or expect the
100
// resource to be fetched or loaded from cache at rewrite time.
101
class TrimWhitespaceSyncFilter : public SimpleTextFilter {
103
static const char kFilterId[];
105
explicit TrimWhitespaceSyncFilter(OutputResourceKind kind,
106
RewriteDriver* driver)
107
: SimpleTextFilter(new TrimWhitespaceRewriter(kind), driver) {
109
virtual ~TrimWhitespaceSyncFilter();
111
virtual void StartElementImpl(HtmlElement* element);
112
virtual const char* id() const { return kFilterId; }
113
virtual const char* name() const { return "TrimWhitespaceSync"; }
116
DISALLOW_COPY_AND_ASSIGN(TrimWhitespaceSyncFilter);
119
// A similarly structured test-filter: this one just upper-cases its text.
120
class UpperCaseRewriter : public SimpleTextFilter::Rewriter {
122
static const char kFilterId[];
124
explicit UpperCaseRewriter(OutputResourceKind kind)
125
: kind_(kind), num_rewrites_(0) {}
126
static SimpleTextFilter* MakeFilter(OutputResourceKind kind,
127
RewriteDriver* driver,
128
UpperCaseRewriter** rewriter_out) {
129
*rewriter_out = new UpperCaseRewriter(kind);
130
return new SimpleTextFilter(*rewriter_out, driver);
133
int num_rewrites() const { return num_rewrites_; }
134
void ClearStats() { num_rewrites_ = 0; }
137
REFCOUNT_FRIEND_DECLARATION(UpperCaseRewriter);
138
virtual ~UpperCaseRewriter();
140
virtual bool RewriteText(const StringPiece& url, const StringPiece& in,
142
ServerContext* server_context) {
144
in.CopyToString(out);
148
virtual HtmlElement::Attribute* FindResourceAttribute(HtmlElement* element) {
149
if (element->keyword() == HtmlName::kLink) {
150
return element->FindAttribute(HtmlName::kHref);
154
virtual OutputResourceKind kind() const { return kind_; }
155
virtual const char* id() const { return kFilterId; }
156
virtual const char* name() const { return "UpperCase"; }
159
OutputResourceKind kind_;
162
DISALLOW_COPY_AND_ASSIGN(UpperCaseRewriter);
165
// Filter that contains nested resources that must themselves
167
class NestedFilter : public RewriteFilter {
169
// For use with NestedFilter constructor
170
static const bool kExpectNestedRewritesSucceed = true;
171
static const bool kExpectNestedRewritesFail = false;
173
static const char kFilterId[];
175
NestedFilter(RewriteDriver* driver, SimpleTextFilter* upper_filter,
176
UpperCaseRewriter* upper_rewriter, bool expected_nested_result)
177
: RewriteFilter(driver), upper_filter_(upper_filter),
178
upper_rewriter_(upper_rewriter), chain_(false),
179
check_nested_rewrite_result_(true),
180
expected_nested_rewrite_result_(expected_nested_result) {
185
int num_top_rewrites() const { return num_top_rewrites_; }
186
int num_sub_rewrites() const { return upper_rewriter_->num_rewrites(); }
189
num_top_rewrites_ = 0;
190
upper_rewriter_->ClearStats();
193
// Set this to true to create a chain of nested rewrites on the same slot.
194
void set_chain(bool x) { chain_ = x; }
196
bool expected_nested_rewrite_result() const {
197
return expected_nested_rewrite_result_;
200
void set_expected_nested_rewrite_result(bool x) {
201
expected_nested_rewrite_result_ = x;
204
void set_check_nested_rewrite_result(bool x) {
205
check_nested_rewrite_result_ = x;
209
virtual ~NestedFilter();
211
class NestedSlot : public ResourceSlot {
213
explicit NestedSlot(const ResourcePtr& resource) : ResourceSlot(resource) {}
214
virtual HtmlElement* element() const { return NULL; }
215
virtual void Render() {}
216
virtual GoogleString LocationString() { return "nested:"; }
219
class Context : public SingleRewriteContext {
221
Context(RewriteDriver* driver, NestedFilter* filter, bool chain)
222
: SingleRewriteContext(driver, NULL, NULL),
227
virtual void RewriteSingle(
228
const ResourcePtr& input, const OutputResourcePtr& output);
229
virtual void Harvest();
232
virtual const char* id() const { return kFilterId; }
233
virtual OutputResourceKind kind() const { return kRewrittenResource; }
236
std::vector<GoogleString*> strings_;
237
NestedFilter* filter_;
239
ResourceSlotVector nested_slots_;
241
DISALLOW_COPY_AND_ASSIGN(Context);
244
RewriteContext* MakeRewriteContext() {
245
return new Context(driver(), this, chain_);
248
void StartElementImpl(HtmlElement* element);
249
SimpleTextFilter* upper_filter() { return upper_filter_; }
251
virtual const char* id() const { return kFilterId; }
252
virtual const char* Name() const { return "NestedFilter"; }
253
virtual void StartDocumentImpl() {}
254
virtual void EndElementImpl(HtmlElement* element) {}
257
// Upper-casing filter we also invoke.
258
SimpleTextFilter* upper_filter_;
259
UpperCaseRewriter* upper_rewriter_;
262
// Whether to check the result of the nested rewrites.
263
bool check_nested_rewrite_result_;
264
// Whether we expect nested rewrites to be successful.
265
bool expected_nested_rewrite_result_;
268
int num_top_rewrites_;
270
DISALLOW_COPY_AND_ASSIGN(NestedFilter);
273
// Simple version of CombineCssFilter.
275
// Concatenates all CSS files loaded from <link> tags into a single output.
276
// Does not consider barriers, @import statements, absolutification, etc.
277
class CombiningFilter : public RewriteFilter {
279
static const char kFilterId[];
281
CombiningFilter(RewriteDriver* driver,
282
MockScheduler* scheduler,
283
int64 rewrite_delay_ms);
284
virtual ~CombiningFilter();
286
class Combiner : public ResourceCombiner {
288
Combiner(RewriteDriver* driver, RewriteFilter* filter)
290
driver, kContentTypeCss.file_extension() + 1, filter) {
292
OutputResourcePtr MakeOutput() {
293
return Combine(rewrite_driver_->message_handler());
295
bool Write(const ResourceVector& in, const OutputResourcePtr& out) {
296
return WriteCombination(in, out, rewrite_driver_->message_handler());
299
virtual bool WritePiece(int index, const Resource* input,
300
OutputResource* combination,
301
Writer* writer, MessageHandler* handler) {
302
writer->Write(prefix_, handler);
303
return ResourceCombiner::WritePiece(
304
index, input, combination, writer, handler);
307
void set_prefix(const GoogleString& prefix) { prefix_ = prefix; }
310
virtual const ContentType* CombinationContentType() {
311
return &kContentTypeCss;
314
GoogleString prefix_;
317
virtual const char* id() const { return kFilterId; }
319
class Context : public RewriteContext {
321
Context(RewriteDriver* driver, CombiningFilter* filter,
322
MockScheduler* scheduler);
324
void AddElement(HtmlElement* element, HtmlElement::Attribute* href,
325
const ResourcePtr& resource) {
326
ResourceSlotPtr slot(Driver()->GetSlot(resource, element, href));
331
virtual bool Partition(OutputPartitions* partitions,
332
OutputResourceVector* outputs);
334
virtual void Rewrite(int partition_index,
335
CachedResult* partition,
336
const OutputResourcePtr& output);
337
virtual bool OptimizationOnly() const {
338
return filter_->optimization_only();
341
void DoRewrite(int partition_index,
342
CachedResult* partition,
343
OutputResourcePtr output);
344
virtual void Render();
345
virtual void WillNotRender();
346
virtual void Cancel();
347
void DisableRemovedSlots(CachedResult* partition);
348
virtual const UrlSegmentEncoder* encoder() const { return &encoder_; }
349
virtual const char* id() const { return kFilterId; }
350
virtual OutputResourceKind kind() const {
351
return filter_->on_the_fly_ ? kOnTheFlyResource : kRewrittenResource;
356
UrlMultipartEncoder encoder_;
357
MockScheduler* scheduler_;
358
int64 time_at_start_of_rewrite_us_;
359
CombiningFilter* filter_;
362
virtual void StartDocumentImpl() {}
363
virtual void StartElementImpl(HtmlElement* element);
364
virtual void Flush() {
365
if (context_.get() != NULL) {
366
driver()->InitiateRewrite(context_.release());
370
virtual void EndElementImpl(HtmlElement* element) {}
371
virtual const char* Name() const { return "Combining"; }
372
RewriteContext* MakeRewriteContext() {
373
return new Context(driver(), this, scheduler_);
375
virtual const UrlSegmentEncoder* encoder() const { return &encoder_; }
377
virtual bool ComputeOnTheFly() const { return on_the_fly_; }
379
int num_rewrites() const { return num_rewrites_; }
380
int num_render() const { return num_render_; }
381
int num_will_not_render() const { return num_will_not_render_; }
382
int num_cancel() const { return num_cancel_; }
384
void ClearStats() { num_rewrites_ = 0; }
385
int64 rewrite_delay_ms() const { return rewrite_delay_ms_; }
386
void set_rewrite_block_on(WorkerTestBase::SyncPoint* sync) {
387
rewrite_block_on_ = sync;
390
void set_rewrite_signal_on(WorkerTestBase::SyncPoint* sync) {
391
rewrite_signal_on_ = sync;
394
// Each entry in combination will be prefixed with this.
395
void set_prefix(const GoogleString& prefix) { prefix_ = prefix; }
397
void set_on_the_fly(bool v) { on_the_fly_ = v; }
399
void set_disable_successors(bool v) { disable_successors_ = v; }
401
bool optimization_only() const { return optimization_only_; }
402
void set_optimization_only(bool o) { optimization_only_ = o; }
405
friend class Context;
407
scoped_ptr<Context> context_;
408
UrlMultipartEncoder encoder_;
409
MockScheduler* scheduler_;
412
int num_will_not_render_;
414
int64 rewrite_delay_ms_;
416
// If this is non-NULL, the actual rewriting will block until this is
417
// signaled. Applied before rewrite_delay_ms_
418
WorkerTestBase::SyncPoint* rewrite_block_on_;
420
// If this is non-NULL, this will be signaled the moment rewrite is called
421
// on the context, before rewrite_block_on_ and rewrite_delay_ms_ are
423
WorkerTestBase::SyncPoint* rewrite_signal_on_;
424
GoogleString prefix_;
425
bool on_the_fly_; // If true, will act as an on-the-fly filter.
426
bool optimization_only_; // If false, will disable load-shedding and fetch
427
// rewrite deadlines.
428
bool disable_successors_; // if true, will disable successors for all
429
// slots, not just mutated ones.
431
DISALLOW_COPY_AND_ASSIGN(CombiningFilter);
434
class RewriteContextTestBase : public RewriteTestBase {
436
static const int64 kRewriteDeadlineMs = 20;
438
// Use a TTL value other than the implicit value, so we are sure we are using
439
// the original TTL value.
440
static const int64 kOriginTtlMs = 12 * Timer::kMinuteMs;
441
// An TTL value that is lower than the default implicit TTL value (300
443
static const int64 kLowOriginTtlMs = 5 * Timer::kSecondMs;
445
// Use a TTL value other than the implicit value, so we are sure we are using
446
// the original TTL value.
447
GoogleString OriginTtlMaxAge() {
448
return StrCat("max-age=", Integer64ToString(
449
kOriginTtlMs / Timer::kSecondMs));
452
RewriteContextTestBase(
453
std::pair<TestRewriteDriverFactory*, TestRewriteDriverFactory*> factories)
454
: RewriteTestBase(factories) {}
455
RewriteContextTestBase() {}
456
virtual ~RewriteContextTestBase();
458
virtual void SetUp();
459
virtual void TearDown();
460
virtual bool AddBody() const { return false; }
461
virtual void ClearStats();
463
void InitResources() { InitResourcesToDomain(kTestDomain); }
464
void InitResourcesToDomain(const char* domain);
465
void InitTrimFilters(OutputResourceKind kind);
466
void InitUpperFilter(OutputResourceKind kind, RewriteDriver* rewrite_driver);
467
void InitCombiningFilter(int64 rewrite_delay_ms);
468
void InitNestedFilter(bool expected_nested_rewrite_result);
470
TrimWhitespaceRewriter* trim_filter_;
471
TrimWhitespaceRewriter* other_trim_filter_;
472
CombiningFilter* combining_filter_;
473
NestedFilter* nested_filter_;
476
} // namespace net_instaweb
478
#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_CONTEXT_TEST_BASE_H_