Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,278 @@
from __future__ import print_function
import jsons
import logging
import os
# Should match size defined in platform/platform_tests/downloader_tests/downloader_test.cpp
BIG_FILE_SIZE = 47684
class Payload:
def __init__(self, message, response_code=200, headers={}):
self.__response_code = response_code
self.__message = message if type(message) is bytes else message.encode('utf8')
self.__headers = headers
def response_code(self):
"""
Response code to send to the client.
"""
return self.__response_code
def message(self):
"""
The message to send to the client.
"""
return self.__message
def length(self):
"""
The length of the response.
"""
return len(self.message())
def headers(self):
"""
The headers to be sent to the client. Please, note, that these do not include
the Content-Length header, which you need to send separately.
"""
return self.__headers
def __repr__(self):
return "{}: {}: {}".format(self.response_code(), self.length(), self.message())
class ResponseProviderMixin:
"""
A mixin (basically, an interface) that the web-server that we might use relies on.
In this implementation, the job of the web-server is just to get the request
(the url and the headers), and to send the response as it knows how. It isn't
its job to decide how to respond to what request. It is the job of the
ResponseProvider.
In your web-server you should initialize the ResponseProvider, and ask it for
response_for_url_and_headers(url, headers)
Which will return a Payload object that the server must send as response.
The server might be notified when a particular request has been received:
got_pinged(self) - someone sent a ping request. The Response provider will
respond with "pong" and call this method of the server. You might want to
increment the count of active users, for ping is the request that new instances
of servers send to check if other servers are currently serving.
kill(self) - someone sent the kill request, which means that that someone
no longer needs this server to serve. You might want to decrement the count of
active users and/or stop the server.
"""
def dispatch_response(self, payload):
"""
Define this mehtod to dispatch the response received from the ResponseProvider
"""
raise NotImplementedError()
def got_pinged(self):
"""
A ping request has been received. In most scenarios it means that the number of
users of this server has increased by 1.
"""
raise NotImplementedError()
def kill(self):
"""
Someone no longer needs this server. Decrement the number of users and stop
the server if the number fell to 0.
"""
raise NotImplementedError()
class ResponseProvider:
def __init__(self, delegate):
self.headers = list()
self.delegate = delegate
self.byterange = None
self.is_chunked = False
self.response_code = 200
def pong(self):
self.delegate.got_pinged()
return Payload("pong")
def my_id(self):
return Payload(str(os.getpid()))
def strip_query(self, url):
query_start = url.find("?")
if (query_start > 0):
return url[:query_start]
return url
def response_for_url_and_headers(self, url, headers):
self.headers = headers
self.chunk_requested()
url = self.strip_query(url)
try:
return {
"/unit_tests/1.txt": self.test1,
"/unit_tests/notexisting_unittest": self.test_404,
"/unit_tests/permanent": self.test_301,
"/unit_tests/47kb.file": self.test_47_kb,
# Following two URIs are used to test downloading failures on different platforms.
"/unit_tests/mac/1234/Uruguay.mwm": self.test_404,
"/unit_tests/linux/1234/Uruguay.mwm": self.test_404,
"/ping": self.pong,
"/kill": self.kill,
"/id": self.my_id,
"/partners/time": self.partners_time,
"/partners/price": self.partners_price,
"/booking/hotelAvailability": self.partners_hotel_availability,
"/booking/deals": self.partners_hotels_with_deals,
"/booking/blockAvailability": self.partners_block_availability,
"/partners/taxi_info": self.partners_yandex_taxi_info,
"/partners/get-offers-in-bbox/": self.partners_rent_nearby,
"/partners/CalculateByCoords": self.partners_calculate_by_coords,
"/gallery/v2/search/": self.promo_gallery_city,
"/single/empty/gallery/v2/search/": self.promo_gallery_city_single_empty,
"/single/gallery/v2/search/": self.promo_gallery_city_single,
"/partners/oauth/token": self.freenow_auth_token,
"/partners/service-types": self.freenow_service_types,
"/gallery/v2/map": self.guides_on_map_gallery,
"/partners/get_supported_tariffs": self.citymobil_supported_tariffs,
"/partners/calculate_price": self.citymobil_calculate_price,
}.get(url, self.test_404)()
except Exception as e:
logging.error("test_server: Can't build server response", exc_info=e)
return self.test_404()
def chunk_requested(self):
if "range" in self.headers:
self.is_chunked = True
self.response_code = 206
meaningful_string = self.headers["range"][6:]
first, last = meaningful_string.split("-")
self.byterange = (int(first), int(last))
def trim_message(self, message):
if not self.is_chunked:
return message
return message[self.byterange[0]: self.byterange[1] + 1]
def test1(self):
init_message = "Test1"
message = self.trim_message(init_message)
size = len(init_message)
self.check_byterange(size)
headers = self.chunked_response_header(size)
return Payload(message, self.response_code, headers)
def test_404(self):
return Payload("", response_code=404)
def test_301(self):
return Payload("", 301, {"Location" : "google.com"})
def check_byterange(self, size):
if self.byterange is None:
self.byterange = (0, size)
def chunked_response_header(self, size):
return {
"Content-Range" : "bytes {start}-{end}/{out_of}".format(start=self.byterange[0],
end=self.byterange[1], out_of=size)
}
def test_47_kb(self):
self.check_byterange(BIG_FILE_SIZE)
headers = self.chunked_response_header(BIG_FILE_SIZE)
message = self.trim_message(self.message_for_47kb_file())
return Payload(message, self.response_code, headers)
def message_for_47kb_file(self):
message = []
for i in range(0, BIG_FILE_SIZE + 1):
message.append(i // 256)
message.append(i % 256)
return bytes(message)
# Partners_api_tests
def partners_time(self):
return Payload(jsons.PARTNERS_TIME)
def partners_price(self):
return Payload(jsons.PARTNERS_PRICE)
def partners_hotel_availability(self):
return Payload(jsons.HOTEL_AVAILABILITY)
def partners_hotels_with_deals(self):
return Payload(jsons.HOTELS_WITH_DEALS)
def partners_block_availability(self):
return Payload(jsons.BLOCK_AVAILABILITY)
def partners_yandex_taxi_info(self):
return Payload(jsons.PARTNERS_TAXI_INFO)
def partners_rent_nearby(self):
return Payload(jsons.PARTNERS_RENT_NEARBY)
def partners_calculate_by_coords(self):
return Payload(jsons.PARTNERS_CALCULATE_BY_COORDS)
def promo_gallery_city(self):
return Payload(jsons.PROMO_GALLERY_CITY)
def promo_gallery_city_single_empty(self):
return Payload(jsons.PROMO_GALLERY_CITY_SINGLE_EMPTY)
def promo_gallery_city_single(self):
return Payload(jsons.PROMO_GALLERY_CITY_SINGLE)
def freenow_auth_token(self):
return Payload(jsons.FREENOW_AUTH_TOKEN)
def freenow_service_types(self):
return Payload(jsons.FREENOW_SERVICE_TYPES)
def guides_on_map_gallery(self):
return Payload(jsons.GUIDES_ON_MAP_GALLERY)
def citymobil_supported_tariffs(self):
return Payload(jsons.CITYMOBIL_SUPPORTED_TARIFFS)
def citymobil_calculate_price(self):
return Payload(jsons.CITYMOBIL_CALCULATE_PRICE)
def kill(self):
logging.debug("Kill called in ResponseProvider")
self.delegate.kill()
return Payload("Bye...")

View file

@ -0,0 +1,180 @@
from __future__ import print_function
import logging
import os
import re
from urllib.error import URLError
from urllib.request import urlopen
import socket
from subprocess import Popen, PIPE
from time import sleep
import sys
import logging
class SiblingKiller:
def __init__(self, port, ping_timeout):
self.all_processes = self.ps_dash_w()
self.all_pids = self.all_process_ids()
self.__allow_serving = False
self.__my_pid = self.my_process_id()
self.port = port
self.ping_timeout = ping_timeout
logging.debug(f"Sibling killer: my process id = {self.__my_pid}")
def allow_serving(self):
return self.__allow_serving
def give_process_time_to_kill_port_user(self):
sleep(5)
def kill_siblings(self):
"""
The idea is to get the list of all processes by the current user, check which one of them is using the port.
If there is such a process, let's wait for 10 seconds for it to start serving, if it doesn't, kill it.
If there is NO such process, let's see if there is a process with the same name as us but with a lower process id.
We shall wait for 10 seconds for it to start, if it doesn't, kill it.
If we are the process with the same name as ours and with the lowest process id, let's start serving and kill everyone else.
"""
if self.wait_for_server():
self.__allow_serving = False
logging.debug("There is a server that is currently serving on our port... Disallowing to start a new one")
return
logging.debug("There are no servers that are currently serving. Will try to kill our siblings.")
sibs = list(self.siblings())
for sibling in sibs:
logging.debug(f"Checking whether we should kill sibling id: {sibling}")
self.give_process_time_to_kill_port_user()
if self.wait_for_server():
serving_pid = self.serving_process_id()
if serving_pid:
logging.debug(f"There is a serving sibling with process id: {serving_pid}")
self.kill(pids=list(map(lambda x: x != serving_pid, sibs)))
self.__allow_serving = False
return
else:
self.kill(pid=sibling)
self.kill_process_on_port() # changes __allow_serving to True if the process was alive and serving
def kill(self, pid=0, pids=[]):
if not pid and not pids:
logging.debug("There are no siblings to kill")
return
if pid and pids:
raise Exception("Use either one pid or multiple pids")
hitlist = ""
if pid:
hitlist = str(pid)
if pids:
hitlist = " ".join(map(str, pids))
command = f"kill -9 {hitlist}"
self.exec_command(command)
def siblings(self):
my_name = self.my_process_name()
return filter(lambda x: x < self.__my_pid,
map(lambda x: int(x.split(" ")[1]),
filter(lambda x: my_name in x, self.all_processes)))
def kill_process_on_port(self):
process_on_port = self.process_using_port(self.port)
if not self.wait_for_server():
self.kill(pid=process_on_port)
self.__allow_serving = True
def all_process_ids(self):
pid = lambda x: int(x.split(" ")[1])
return map(pid, self.all_processes)
def process_using_port(self, port):
def isListenOnPort(pid):
info_line = self.exec_command(f"lsof -a -p{pid} -i4")
return info_line.endswith("(LISTEN)") and str(port) in info_line
listening_process = list(filter(isListenOnPort, self.all_pids))
if len(listening_process) > 1:
pass
# We should panic here
if not listening_process:
return None
return listening_process[0]
def my_process_id(self):
return os.getpid()
def my_process_name(self):
return " ".join(sys.argv)
def ps_dash_w(self):
not_header = lambda x: x and not x.startswith("UID")
output = self.exec_command("ps -f").split("\n")
return list(filter(not_header, list(re.sub("\s{1,}", " ", x.strip()) for x in output)))
def wait_for_server(self):
for i in range(0, 2):
if self.ping(): # unsuccessful ping takes 5 seconds (look at PING_TIMEOUT) iff there is a dead server occupying the port
return True
return False
def ping(self):
html = None
try:
response = urlopen(f"http://localhost:{self.port}/ping", timeout=self.ping_timeout)
html = response.read()
except (URLError, socket.timeout):
pass
logging.debug(f"Pinging returned html: {html}")
return html == "pong"
def serving_process_id(self):
try:
response = urlopen(f"http://localhost:{self.port}/id", timeout=self.ping_timeout)
resp = response.read()
id = int(resp)
return id
except:
logging.info("Couldn't get id of a serving process (the PID of the server that responded to pinging)")
return None
def exec_command(self, command):
logging.debug(f">> {command}")
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, text=True)
output, err = p.communicate()
p.wait()
return output

View file

@ -0,0 +1,8 @@
PORT = 34568
# timeout for the self destruction timer - how much time
# passes between the last request and the server killing
# itself
LIFESPAN = 180.0
PING_TIMEOUT = 5 # Nubmer of seconds to wait for ping response

View file

@ -0,0 +1,957 @@
# coding=utf-8
PARTNERS_PRICE = """
{
"prices": [
{
"currency_code": "USD",
"display_name": "POOL",
"distance": 10.93,
"duration": 1320,
"estimate": "$13-17",
"high_estimate": 18,
"localized_display_name": "POOL",
"low_estimate": 13,
"minimum": null,
"product_id": "bc300c14-c30d-4d3f-afcb-19b240c16a13",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "uberX",
"distance": 10.93,
"duration": 1320,
"estimate": "$15-21",
"high_estimate": 21,
"localized_display_name": "uberX",
"low_estimate": 15,
"minimum": 6,
"product_id": "dee8691c-8b48-4637-b048-300eee72d58d",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "uberX + Car Seat",
"distance": 10.93,
"duration": 1320,
"estimate": "$26-31",
"high_estimate": 31,
"localized_display_name": "uberX + Car Seat",
"low_estimate": 26,
"minimum": 15,
"product_id": "bc98a16f-ad72-41a3-8624-809ce654ac57",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "uberXL",
"distance": 10.93,
"duration": 1320,
"estimate": "$27-36",
"high_estimate": 36,
"localized_display_name": "uberXL",
"low_estimate": 27,
"minimum": 7,
"product_id": "9ffa937e-7d2e-4bcf-bc2b-ffec4ef24380",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "UberBLACK",
"distance": 10.93,
"duration": 1320,
"estimate": "$48-63",
"high_estimate": 63,
"localized_display_name": "UberBLACK",
"low_estimate": 48,
"minimum": 15,
"product_id": "a52a9012-d73e-4127-8440-f273cddfd307",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "BLACK CAR + Car Seat",
"distance": 10.93,
"duration": 1320,
"estimate": "$58-73",
"high_estimate": 73,
"localized_display_name": "BLACK CAR + Car Seat",
"low_estimate": 58,
"minimum": 25,
"product_id": "2a299c73-098d-47cd-b32c-825cb155f82a",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "UberSUV",
"distance": 10.93,
"duration": 1320,
"estimate": "$59-75",
"high_estimate": 75,
"localized_display_name": "UberSUV",
"low_estimate": 59,
"minimum": 25,
"product_id": "4e6fd14c-3866-40f1-b173-f12aeb8fbbd0",
"surge_multiplier": 1.0
},
{
"currency_code": "USD",
"display_name": "SUV + Car Seat",
"distance": 10.93,
"duration": 1320,
"estimate": "$65-80",
"high_estimate": 80,
"localized_display_name": "SUV + Car Seat",
"low_estimate": 65,
"minimum": 35,
"product_id": "74766497-b951-4eae-98c9-a67d87e2c0c4",
"surge_multiplier": 1.0
},
{
"currency_code": null,
"display_name": "Wheelchair",
"distance": 10.93,
"duration": 1320,
"estimate": "Metered",
"high_estimate": null,
"localized_display_name": "Wheelchair",
"low_estimate": null,
"minimum": null,
"product_id": "89f38d7a-d184-4054-9f2e-6b57c94143d6",
"surge_multiplier": 1.0
},
{
"currency_code": null,
"display_name": "uberTAXI",
"distance": 10.93,
"duration": 1320,
"estimate": "Metered",
"high_estimate": null,
"localized_display_name": "uberTAXI",
"low_estimate": null,
"minimum": null,
"product_id": "f67c83fb-4668-42eb-9aa1-ab32e710c8bf",
"surge_multiplier": 1.0
}
]
}
"""
PARTNERS_TIME = """
{
"times": [
{
"display_name": "POOL",
"estimate": 360,
"localized_display_name": "POOL",
"product_id": "bc300c14-c30d-4d3f-afcb-19b240c16a13"
},
{
"display_name": "uberX",
"estimate": 300,
"localized_display_name": "uberX",
"product_id": "dee8691c-8b48-4637-b048-300eee72d58d"
},
{
"display_name": "uberXL",
"estimate": 360,
"localized_display_name": "uberXL",
"product_id": "9ffa937e-7d2e-4bcf-bc2b-ffec4ef24380"
},
{
"display_name": "UberBLACK",
"estimate": 300,
"localized_display_name": "UberBLACK",
"product_id": "a52a9012-d73e-4127-8440-f273cddfd307"
},
{
"display_name": "BLACK CAR + Car Seat",
"estimate": 300,
"localized_display_name": "BLACK CAR + Car Seat",
"product_id": "2a299c73-098d-47cd-b32c-825cb155f82a"
},
{
"display_name": "UberSUV",
"estimate": 300,
"localized_display_name": "UberSUV",
"product_id": "4e6fd14c-3866-40f1-b173-f12aeb8fbbd0"
},
{
"display_name": "SUV + Car Seat",
"estimate": 300,
"localized_display_name": "SUV + Car Seat",
"product_id": "74766497-b951-4eae-98c9-a67d87e2c0c4"
}
]
}
"""
HOTEL_AVAILABILITY = """
{
"result": [
{
"hotel_currency_code": "EUR",
"hotel_id": 10623,
"price": 801
},
{
"hotel_currency_code": "USD",
"hotel_id": 10624,
"price": 802
},
{
"hotel_currency_code": "RUR",
"hotel_id": 10625,
"price": 803
}
]
}
"""
HOTELS_WITH_DEALS = """
{
"result": [
{
"hotel_currency_code": "EUR",
"hotel_id": 10622,
"price": 801
},
{
"hotel_currency_code": "USD",
"hotel_id": 10624,
"price": 802
},
{
"hotel_currency_code": "RUR",
"hotel_id": 10626,
"price": 803
}
]
}
"""
BLOCK_AVAILABILITY = """
{
"result": [
{
"direct_payment": true,
"checkin": "2018-06-16",
"hotel_id": 61394,
"block": [
{
"room_description": "Более просторные апартаменты-студио с кухней открытой планировки, телевизором с плоским экраном и бесплатным Wi-Fi.По запросу предоставляется DVD-плеер.",
"taxes": "НДС в размере 7 % , городской налог в размере 5 % ",
"rack_rate": {
"currency": "EUR",
"price": 0,
"other_currency": {
"currency": "RUB",
"price": 0
}
},
"block_id": "6139409_116589412_2_1_0",
"max_occupancy": 2,
"refundable": false,
"breakfast_included": true,
"is_smart_deal": false,
"incremental_price": [
{
"other_currency": {
"currency": "RUB",
"price": 8405.46
},
"price": 116,
"currency": "EUR"
}
],
"photos": [
{
"url_original": "https://q-xx.bstatic.com/images/hotel/max500/437/43793388.jpg",
"photo_id": 43793388,
"url_max300": "https://q-xx.bstatic.com/images/hotel/max300/437/43793388.jpg",
"url_square60": "https://q-xx.bstatic.com/images/hotel/square60/437/43793388.jpg"
}
],
"deposit_required": false,
"name": "Синьо Студио 1 - Стоимость не возвращается",
"is_last_minute_deal": false,
"min_price": {
"other_currency": {
"currency": "RUB",
"price": 8405.46
},
"price": 116,
"currency": "EUR"
},
"refundable_until": "",
"room_id": 6139409
}
],
"checkout": "2018-06-17"
}
]
}
"""
PARTNERS_TAXI_INFO = """
{
"currency": "RUB",
"distance": 6888.846981748964,
"options": [
{
"class_level": 50,
"class_name": "econom",
"min_price": 129,
"price": 344,
"waiting_time": 527.8793726078095
},
{
"class_level": 70,
"class_name": "business",
"min_price": 239,
"price": 504,
"waiting_time": 76.37023611385494
},
{
"class_level": 100,
"class_name": "comfortplus",
"min_price": 239,
"price": 557,
"waiting_time": 99.0058955445591
},
{
"class_level": 200,
"class_name": "minivan",
"min_price": 239,
"price": 532,
"waiting_time": 322.77413167989687
},
{
"class_level": 300,
"class_name": "vip",
"min_price": 359,
"price": 799,
"waiting_time": 223.34814145904883
}
],
"time": 1057.7440430297368
}
"""
PARTNERS_RENT_NEARBY = """
{
"clusters": [
{
"lat": 55.80529,
"lng": 37.508274,
"offers": [
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 50000.0,
"floorNumber": 8,
"floorsCount": 9,
"photosCount": 11,
"url": "https://cian.ru/rent/flat/159026341",
"address": "Ленинградский просп., 77К2"
},
{
"flatType": "rooms",
"roomsCount": 1,
"priceRur": 39999.0,
"floorNumber": 1,
"floorsCount": 12,
"photosCount": 13,
"url": "https://cian.ru/rent/flat/157827964",
"address": "Ленинградский просп., 77К2"
},
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 58000.0,
"floorNumber": 6,
"floorsCount": 9,
"photosCount": 9,
"url": "https://cian.ru/rent/flat/159523671",
"address": "Ленинградский просп., 77К2"
}
],
"count": 3,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=26696"
},
{
"lat": 55.805776,
"lng": 37.50946,
"offers": [
{
"flatType": "rooms",
"roomsCount": 1,
"priceRur": 39000.0,
"floorNumber": 2,
"floorsCount": 9,
"photosCount": 9,
"url": "https://cian.ru/rent/flat/158786442",
"address": "Ленинградский просп., 77А"
}
],
"count": 1,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=2009028"
},
{
"lat": 55.808306,
"lng": 37.5008,
"offers": [
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 50000.0,
"floorNumber": 6,
"floorsCount": 10,
"photosCount": 9,
"url": "https://cian.ru/rent/flat/155419837",
"address": "Волоколамское ш., 6"
}
],
"count": 1,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=1692086"
},
{
"lat": 55.805999,
"lng": 37.503738,
"offers": [
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 70000.0,
"floorNumber": 4,
"floorsCount": 9,
"photosCount": 0,
"url": "https://cian.ru/rent/flat/159765700",
"address": "Волоколамское ш., 1кА"
}
],
"count": 1,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=1009214"
},
{
"lat": 55.805361,
"lng": 37.507124,
"offers": [
{
"flatType": "rooms",
"roomsCount": 1,
"priceRur": 45000.0,
"floorNumber": 1,
"floorsCount": 9,
"photosCount": 6,
"url": "https://cian.ru/rent/flat/158673214",
"address": "Ленинградский просп., 77К3"
},
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 48000.0,
"floorNumber": 5,
"floorsCount": 9,
"photosCount": 15,
"url": "https://cian.ru/rent/flat/158613232",
"address": "Ленинградский просп., 77К3"
},
{
"flatType": "rooms",
"roomsCount": 1,
"priceRur": 45000.0,
"floorNumber": 1,
"floorsCount": 9,
"photosCount": 7,
"url": "https://cian.ru/rent/flat/159035369",
"address": "Ленинградский просп., 77К3"
}
],
"count": 3,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=26698"
},
{
"lat": 55.809226,
"lng": 37.504978,
"offers": [
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 60000.0,
"floorNumber": 5,
"floorsCount": 8,
"photosCount": 37,
"url": "https://cian.ru/rent/flat/157212858",
"address": "Ленинградское ш., 3С1"
},
{
"flatType": "rooms",
"roomsCount": 2,
"priceRur": 35000.0,
"floorNumber": 6,
"floorsCount": 8,
"photosCount": 5,
"url": "https://cian.ru/rent/flat/158689565",
"address": "Ленинградское ш., 3С1"
}
],
"count": 2,
"url": "https://cian.ru/cat.php?deal_type=rent&offer_type=flat&engine_version=2&house%5B0%5D=583624"
}
]
}
"""
PARTNERS_CALCULATE_BY_COORDS = """
{
"ButtonText": null,
"Message": null,
"PaymentMethods": null,
"Prices": null,
"ShowFrom": false,
"Success": true,
"TypeId": 2,
"CurrencyCode": "RUB",
"FeedTime": 9,
"Price": 244,
"PriceString": "244.00 ₽"
}
"""
PROMO_GALLERY_CITY = """
{
"data": [
{
"url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca",
"name": "По Виа Рипетта до мавзолея Августа и Алтаря мира",
"author": {
"key_id": "00000000-0000-0000-0000-000000000000",
"name": "The Village"
},
"image_url": "http://localhost:8000/images/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca.jpg",
"access": "public",
"lux_category": {
"name": "LUX",
"color": "666666"
},
"tier": "price.tier"
},
{
"url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca",
"name": "Полеты в метро",
"author": {
"key_id": "00000000-0000-0000-0000-000000000000",
"name": "Bmj"
},
"access": "public",
"lux_category": {
"color": "000000"
}
}
],
"errors": [],
"meta": {
"more": "search?city=888"
}
}
"""
PROMO_GALLERY_CITY_SINGLE_EMPTY = """
{
"data": [
{
"url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca",
"name": "По Виа Рипетта до мавзолея Августа и Алтаря мира",
"author": {
"key_id": "00000000-0000-0000-0000-000000000000",
"name": "The Village"
},
"image_url": "http://localhost:8000/images/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca.jpg",
"access": "public",
"lux_category": {
"name": "LUX",
"color": "666666"
},
"tier": "price.tier"
}
],
"errors": [],
"meta": {
"more": "search?city=888"
}
}
"""
PROMO_GALLERY_CITY_SINGLE = """
{
"data": [
{
"url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca",
"name": "По Виа Рипетта до мавзолея Августа и Алтаря мира",
"author": {
"key_id": "00000000-0000-0000-0000-000000000000",
"name": "The Village"
},
"image_url": "http://localhost:8000/images/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca.jpg",
"access": "public",
"lux_category": {
"name": "LUX",
"color": "666666"
},
"tier": "price.tier",
"place": {
"name": "Bookmark name",
"description": "Bookmark description"
}
}
],
"errors": [],
"meta": {
"more": "search?city=888"
}
}
"""
FREENOW_AUTH_TOKEN = """
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV",
"token_type": "bearer",
"expires_in": 600,
"scope": "service-types"
}
"""
FREENOW_SERVICE_TYPES = """
{
"serviceTypes": [
{
"id": "TAXI",
"type": "TAXI",
"displayName": "Taxi",
"eta": {
"value": 0,
"displayValue": "0 Minutes"
},
"fare": {
"type": "FIXED",
"value": 5000,
"currencyCode": "GBP",
"displayValue": "5000GBP"
},
"availablePaymentMethodTypes": [
"BUSINESS_ACCOUNT",
"CREDIT_CARD",
"PAYPAL",
"CASH"
],
"seats": {
"max": 4,
"values": [],
"displayValue": "4"
},
"availableBookingOptions": [
{
"name": "COMMENT",
"displayName": "COMMENT",
"type": "TEXT"
},
{
"name": "MERCEDES",
"displayName": "MERCEDES",
"type": "BOOLEAN"
},
{
"name": "FAVORITE_DRIVER",
"displayName": "FAVORITE_DRIVER",
"type": "BOOLEAN"
},
{
"name": "FIVE_STARS",
"displayName": "FIVE_STARS",
"type": "BOOLEAN"
},
{
"name": "SMALL_ANIMAL",
"displayName": "SMALL_ANIMAL",
"type": "BOOLEAN"
}
]
}
]
}
"""
GUIDES_ON_MAP_GALLERY = """
{
"data": [
{
"point": {
"lat": 12.345678,
"lon": 91.234567
},
"bundle_counts": {
"sights": 1,
"outdoor": 0
},
"extra": {
"server_id": "120-3957012735012rffasfaf",
"name": "hello",
"image_url": "world",
"tag": "tag",
"bookmark_count": 100,
"has_track": true,
"tracks_length": 1234.11,
"tour_duration": {
"hours": 5,
"minutes": 17
},
"ascent": -300
}
},
{
"point": {
"lat": 91.234567,
"lon": 12.345678
},
"bundle_counts": {
"sights": 1,
"outdoor": 8
},
"extra": null
}
],
"meta": {
"suggested_zoom_level": 5
}
}
"""
CITYMOBIL_SUPPORTED_TARIFFS = """
{
"tariff_groups": [
{
"tariff_group_id": 2,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "Таксипортация для ежедневных поездок",
"luxury_level": 1,
"car_models": "Kia Rio, Hyundai Solaris, VW Polo",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/Polo.png",
"fastest_car_image": "https://external-storage.city-mobil.ru/generated/storage_files/econom_vw.png"
}
},
{
"tariff_group_id": 4,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "Комфортная таксипортация",
"luxury_level": 3,
"car_models": "Skoda Octavia, Hyundai Elantra, Kia Cerato",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/Octavia.png",
"fastest_car_image": "https://external-storage.city-mobil.ru/generated/storage_files/comfort_shkoda.png"
}
},
{
"tariff_group_id": 13,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "Таксипортация с повышенным комфортом",
"luxury_level": 4,
"car_models": "Kia Optima, Toyota Camry, Hyundai Sonata",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/Optima.png",
"fastest_car_image": "https://external-storage.city-mobil.ru/generated/storage_files/comfort_plus_kia.png"
}
},
{
"tariff_group_id": 5,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "Для важных встреч",
"luxury_level": 5,
"car_models": "BMW 5, Mercedes E-klasse, Audi A6",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/E-Klasse.png",
"fastest_car_image": "https://external-storage.city-mobil.ru/generated/storage_files/buisness_merce.png"
}
},
{
"tariff_group_id": 3,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "",
"luxury_level": 0,
"car_models": "Ford Focus, Kia Rio, Nissan Almera",
"car_image": "",
"fastest_car_image": ""
}
},
{
"tariff_group_id": 7,
"tariff_options": [],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": true,
"is_options_enabled": true,
"order_time_type": [
"asap",
"delay"
],
"description": "Таксипортация для большой компании",
"luxury_level": -1,
"car_models": "Ford Galaxy, Citroen C4 Picasso, Chevrolet Orlando",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/C4.png",
"fastest_car_image": "https://external-storage.city-mobil.ru/generated/storage_files/miniven_citrienC4.png"
}
},
{
"tariff_group_id": 27,
"tariff_options": [
{
"id": 99,
"title": "От двери до двери",
"type": "delivery_tariff_door_to_door",
"is_toggle_selection": true,
"description": "Водитель сам заберет посылку у вас и доставит ее адресату до квартиры"
}
],
"tariff_detail": {
"is_waypoints_enabled": false,
"is_by_instruction_enabled": false,
"is_options_enabled": false,
"order_time_type": [
"asap"
],
"description": "",
"luxury_level": -1,
"car_models": "Когда можно не ехать, но нужно передать документы, ключи или чемодан",
"car_image": "https://external-storage.city-mobil.ru/generated/storage_files/Delivery_day.png",
"fastest_car_image": ""
}
}
]
}
"""
CITYMOBIL_CALCULATE_PRICE = """
{
"distance_text": "8 км",
"duration_text": "15 мин",
"id_calculation": "494d1fc56850ae4411e86fe2d902abe9",
"prices": [
{
"coefficient": 1,
"fixed_price": true,
"has_discount": false,
"id_tariff": 4098,
"id_tariff_group": 2,
"label": "763₽. В пути ~15 мин",
"new_user_discount": false,
"price": 763,
"total_price": 763,
"tariff_info": {
"name": "Эконом",
"price": "763₽. В пути ~15 мин",
"car_models": "Kia Rio, Hyundai Solaris, VW Polo, Renault Logan, Skoda Rapid, Nissan Almera, Chevrolet Aveo, Ford Focus.",
"car_capacity": "Пассажиров: 4, мест багажа: 2",
"link": "https://t.city-mobil.ru/view/tariffs/2/ru?id_tariff=4098",
"details": [
{
"text": "Стоимость поездки",
"value": "763₽"
}
]
}
},
{
"coefficient": 1,
"fixed_price": true,
"has_discount": false,
"id_tariff": 2,
"id_tariff_group": 4,
"label": "816₽. В пути ~15 мин",
"new_user_discount": false,
"price": 816,
"total_price": 816,
"tariff_info": {
"name": "Комфорт",
"price": "816₽. В пути ~15 мин",
"car_models": "Skoda Octavia, Hyundai Elantra, Kia Cerato, Toyota Camry, Nissan Teana, Ford Mondeo, Kia Ceed, Hyundai Sonata",
"car_capacity": "Пассажиров: 4, мест багажа: 2. Кондиционер обязателен.",
"link": "https://t.city-mobil.ru/view/tariffs/2/ru?id_tariff=2",
"details": [
{
"text": "Стоимость поездки",
"value": "816₽"
}
]
}
},
{
"coefficient": 1,
"fixed_price": true,
"has_discount": false,
"id_tariff": 144,
"id_tariff_group": 5,
"label": "1000₽. В пути ~15 мин",
"new_user_discount": false,
"price": 1000,
"total_price": 1000,
"tariff_info": {
"name": "Бизнес",
"price": "1000₽. В пути ~15 мин",
"car_models": "BMW 5, Mercedes E-klasse, Audi A6, Lexus GS, Hyundai Equus",
"car_capacity": "Пассажиров: 4, мест багажа: 2",
"link": "https://t.city-mobil.ru/view/tariffs/2/ru?id_tariff=144",
"details": [
{
"text": "Стоимость поездки",
"value": "1000₽"
}
]
}
}
],
"route": {
"distance": 7250,
"duration": 846,
"points": ""
},
"service_status": 1,
"eta": 600
}
"""

View file

@ -0,0 +1,213 @@
"""
This is a simple web-server that does very few things. It is necessary for
the downloader tests.
Here is the logic behind the initialization:
Because several instances of the test can run simultaneously on the Build
machine, we have to take this into account and not start another server if
one is already running. However, there is a chance that a server will not
terminate correctly, and will still hold the port, so we will not be able
to initialize another server.
So before initializing the server, we check if any processes are using the port
that we want to use. If we find such a process, we assume that it might be
working, and wait for about 10 seconds for it to start serving. If it does not,
we kill it.
Next, we check the name of our process and see if there are other processes
with the same name. If there are, we assume that they might start serving any
moment. So we iterate over the ones that have PID lower than ours, and wait
for them to start serving. If a process doesn't serve, we kill it.
If we have killed (or someone has) all the processes with PIDs lower than ours,
we try to start serving. If we succeed, we kill all other processes with the
same name as ours. If we don't someone else will kill us.
"""
from __future__ import print_function
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from ResponseProvider import Payload
from ResponseProvider import ResponseProvider
from ResponseProvider import ResponseProviderMixin
from SiblingKiller import SiblingKiller
from threading import Timer
from config import LIFESPAN, PING_TIMEOUT, PORT
import os
import socket
import threading
import traceback
import logging
import logging.config
try:
from tornado_handler import MainHandler
USE_TORNADO = True
except:
USE_TORNADO = False
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
class InternalServer(HTTPServer):
def kill_me(self):
self.shutdown()
logging.info(f"The server's life has come to an end, pid: {os.getpid()}")
def reset_selfdestruct_timer(self):
if self.self_destruct_timer:
self.self_destruct_timer.cancel()
self.self_destruct_timer = Timer(LIFESPAN, self.kill_me)
self.self_destruct_timer.start()
def __init__(self, server_address, RequestHandlerClass,
bind_and_activate=True):
HTTPServer.__init__(self, server_address, RequestHandlerClass,
bind_and_activate=bind_and_activate)
self.self_destruct_timer = None
self.clients = 1
self.reset_selfdestruct_timer()
def suicide(self):
self.clients -= 1
if self.clients == 0:
if self.self_destruct_timer is not None:
self.self_destruct_timer.cancel()
quick_and_painless_timer = Timer(0.1, self.kill_me)
quick_and_painless_timer.start()
class TestServer:
def __init__(self):
self.may_serve = False
pid = os.getpid()
logging.info(f"Init server. Pid: {pid}")
self.server = None
killer = SiblingKiller(PORT, PING_TIMEOUT)
killer.kill_siblings()
if killer.allow_serving():
try:
self.init_server()
logging.info(f"Started server with pid: {pid}")
self.may_serve = True
except socket.error:
logging.info("Failed to start the server: Port is in use")
except Exception as e:
logging.debug(e)
logging.info("Failed to start serving for unknown reason")
traceback.print_exc()
else:
logging.info(f"Not allowed to start serving for process: {pid}")
def init_server(self):
if USE_TORNADO:
MainHandler.init_server(PORT, LIFESPAN)
else:
print("""
*************
WARNING: Using the python's built-in BaseHTTPServer!
It is all right if you run the tests on your local machine, but if you are running tests on a server,
please consider installing Tornado. It is a much more powerful web-server. Otherwise you will find
that some of your downloader tests either fail or hang.
do
sudo pip install tornado
or go to http://www.tornadoweb.org/en/stable/ for more detail.
*************
""")
self.server = InternalServer(('localhost', PORT), PostHandler)
def start_serving(self):
if not self.may_serve:
return
if USE_TORNADO:
MainHandler.start_serving()
else:
thread = threading.Thread(target=self.server.serve_forever)
thread.deamon = True
thread.start()
class PostHandler(BaseHTTPRequestHandler, ResponseProviderMixin):
def dispatch_response(self, payload):
self.send_response(payload.response_code())
for h in payload.headers():
self.send_header(h, payload.headers()[h])
self.send_header("Content-Length", payload.length())
self.end_headers()
self.wfile.write(payload.message())
def init_vars(self):
self.response_provider = ResponseProvider(self)
def do_POST(self):
self.init_vars()
self.server.reset_selfdestruct_timer()
headers = self.prepare_headers()
payload = self.response_provider.response_for_url_and_headers(self.path, headers)
if payload.response_code() >= 300:
length = int(self.headers.get('content-length'))
self.dispatch_response(Payload(self.rfile.read(length)))
else:
self.dispatch_response(payload)
def do_GET(self):
headers = self.prepare_headers()
self.init_vars()
self.dispatch_response(self.response_provider.response_for_url_and_headers(self.path, headers))
def prepare_headers(self):
ret = dict()
for h in self.headers:
ret[h.lower()] = self.headers.get(h)
return ret
def got_pinged(self):
self.server.clients += 1
def kill(self):
logging.debug("Kill called in testserver")
self.server.suicide()
if __name__ == '__main__':
server = TestServer()
server.start_serving()

View file

@ -0,0 +1,99 @@
from __future__ import print_function
from ResponseProvider import Payload
from ResponseProvider import ResponseProvider
from ResponseProvider import ResponseProviderMixin
from threading import Timer
import os
import threading
import tornado.ioloop
import tornado.web
import logging
class MainHandler(tornado.web.RequestHandler, ResponseProviderMixin):
ping_count = 1
self_destruct_timer = None
def got_pinged(self):
MainHandler.ping_count += 1
def kill(self):
MainHandler.ping_count -= 1
if MainHandler.ping_count <= 0: #so that if we decrease the value from several threads we still kill it.
MainHandler.suicide()
if MainHandler.self_destruct_timer:
MainHandler.self_destruct_timer.cancel()
def dispatch_response(self, payload):
self.set_status(payload.response_code())
for h in payload.headers():
self.add_header(h, payload.headers()[h])
self.add_header("Content-Length", payload.length())
self.write(payload.message())
def prepare_headers(self):
ret = dict()
for h in self.request.headers:
ret[h.lower()] = self.request.headers.get(h)
return ret
def init_vars(self):
self.response_provider = ResponseProvider(self)
self.headers = self.prepare_headers()
def prepare(self):
MainHandler.reset_self_destruct_timer()
self.init_vars()
def get(self, param):
self.dispatch_response(self.response_provider.response_for_url_and_headers(self.request.uri, self.headers))
def post(self, param):
payload = self.response_provider.response_for_url_and_headers(self.request.uri, self.headers)
if payload.response_code() >= 300:
self.dispatch_response(Payload(self.request.body))
else:
self.dispatch_response(payload)
@staticmethod
def suicide():
tornado.ioloop.IOLoop.current().stop()
logging.info("The server's life has come to an end, pid: {}".format(os.getpid()))
@staticmethod
def reset_self_destruct_timer():
if MainHandler.self_destruct_timer:
logging.debug("Canceling the kill timer")
MainHandler.self_destruct_timer.cancel()
MainHandler.self_destruct_timer = Timer(MainHandler.lifespan, MainHandler.suicide)
logging.debug("Starting the kill timer")
MainHandler.self_destruct_timer.start()
@staticmethod
def start_serving():
thread = threading.Thread(target=tornado.ioloop.IOLoop.current().start)
thread.deamon = True
thread.start()
@staticmethod
def init_server(port, lifespan):
MainHandler.lifespan = lifespan
MainHandler.reset_self_destruct_timer()
application = tornado.web.Application([
(r"/(.*)", MainHandler),
])
application.listen(port)